/*-------------------------------------------------------------------------
 *
 * partbounds.c
 *		用于操作分区边界的支持例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *		  src/backend/partitioning/partbounds.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/relation.h"
#include "access/table.h"
#include "access/tableam.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_type.h"
#include "commands/tablecmds.h"
#include "common/hashfn.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/pathnodes.h"
#include "parser/parse_coerce.h"
#include "partitioning/partbounds.h"
#include "partitioning/partdesc.h"
#include "partitioning/partprune.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/partcache.h"
#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"

/*
 * 当对从目录读取的分区边界进行qsort时，每个边界
 * 都由以下结构之一表示。
 */

/* 哈希分区的一个边界 */
typedef struct PartitionHashBound
{
	int			modulus;
	int			remainder;
	int			index;
} PartitionHashBound;

/* 来自某个（第index个）列表分区的一个值 */
typedef struct PartitionListValue
{
	int			index;
	Datum		value;
} PartitionListValue;

/* 范围分区的一个边界 */
typedef struct PartitionRangeBound
{
	int			index;
	Datum	   *datums;			/* 范围边界数据 */
	PartitionRangeDatumKind *kind;	/* 每个数据的类型 */
	bool		lower;			/* 这是下边界（与上边界相对） */
} PartitionRangeBound;

/*
 * 从连接关系的分区映射到正在计算的连接
 * 关系的分区（即合并分区）
 */
typedef struct PartitionMap
{
	int			nparts;			/* 分区数量 */
	int		   *merged_indexes; /* 合并分区的索引 */
	bool	   *merged;			/* 标志以指示分区是否
								 * 与非虚拟分区合并 */
	bool		did_remapping;	/* 我们是否重新映射了分区？ */
	int		   *old_indexes;	/* 如果进行了重新映射，则为
								 * 合并分区的旧索引 */
} PartitionMap;

/* 比较两个范围边界的宏 */
#define compare_range_bounds(partnatts, partsupfunc, partcollations, \
							 bound1, bound2) \
	(fc_partition_rbound_cmp(partnatts, partsupfunc, partcollations, \
						  (bound1)->datums, (bound1)->kind, (bound1)->lower, \
						  bound2))

static int32 fc_qsort_partition_hbound_cmp(const void *fc_a, const void *fc_b);
static int32 fc_qsort_partition_list_value_cmp(const void *fc_a, const void *fc_b,
											void *fc_arg);
static int32 fc_qsort_partition_rbound_cmp(const void *fc_a, const void *fc_b,
										void *fc_arg);
static PartitionBoundInfo fc_create_hash_bounds(PartitionBoundSpec **fc_boundspecs,
											 int fc_nparts, PartitionKey fc_key, int **fc_mapping);
static PartitionBoundInfo fc_create_list_bounds(PartitionBoundSpec **fc_boundspecs,
											 int fc_nparts, PartitionKey fc_key, int **fc_mapping);
static PartitionBoundInfo fc_create_range_bounds(PartitionBoundSpec **fc_boundspecs,
											  int fc_nparts, PartitionKey fc_key, int **fc_mapping);
static PartitionBoundInfo fc_merge_list_bounds(FmgrInfo *fc_partsupfunc,
											Oid *fc_collations,
											RelOptInfo *fc_outer_rel,
											RelOptInfo *fc_inner_rel,
											JoinType fc_jointype,
											List **fc_outer_parts,
											List **fc_inner_parts);
static PartitionBoundInfo fc_merge_range_bounds(int fc_partnatts,
											 FmgrInfo *fc_partsupfuncs,
											 Oid *fc_partcollations,
											 RelOptInfo *fc_outer_rel,
											 RelOptInfo *fc_inner_rel,
											 JoinType fc_jointype,
											 List **fc_outer_parts,
											 List **fc_inner_parts);
static void fc_init_partition_map(RelOptInfo *fc_rel, PartitionMap *fc_map);
static void fc_free_partition_map(PartitionMap *fc_map);
static bool fc_is_dummy_partition(RelOptInfo *fc_rel, int fc_part_index);
static int	fc_merge_matching_partitions(PartitionMap *fc_outer_map,
									  PartitionMap *fc_inner_map,
									  int fc_outer_part,
									  int fc_inner_part,
									  int *fc_next_index);
static int	fc_process_outer_partition(PartitionMap *fc_outer_map,
									PartitionMap *fc_inner_map,
									bool fc_outer_has_default,
									bool fc_inner_has_default,
									int fc_outer_index,
									int fc_inner_default,
									JoinType fc_jointype,
									int *fc_next_index,
									int *fc_default_index);
static int	fc_process_inner_partition(PartitionMap *fc_outer_map,
									PartitionMap *fc_inner_map,
									bool fc_outer_has_default,
									bool fc_inner_has_default,
									int fc_inner_index,
									int fc_outer_default,
									JoinType fc_jointype,
									int *fc_next_index,
									int *fc_default_index);
static void fc_merge_null_partitions(PartitionMap *fc_outer_map,
								  PartitionMap *fc_inner_map,
								  bool fc_outer_has_null,
								  bool fc_inner_has_null,
								  int fc_outer_null,
								  int fc_inner_null,
								  JoinType fc_jointype,
								  int *fc_next_index,
								  int *fc_null_index);
static void fc_merge_default_partitions(PartitionMap *fc_outer_map,
									 PartitionMap *fc_inner_map,
									 bool fc_outer_has_default,
									 bool fc_inner_has_default,
									 int fc_outer_default,
									 int fc_inner_default,
									 JoinType fc_jointype,
									 int *fc_next_index,
									 int *fc_default_index);
static int	fc_merge_partition_with_dummy(PartitionMap *fc_map, int fc_index,
									   int *fc_next_index);
static void fc_fix_merged_indexes(PartitionMap *fc_outer_map,
							   PartitionMap *fc_inner_map,
							   int fc_nmerged, List *fc_merged_indexes);
static void fc_generate_matching_part_pairs(RelOptInfo *fc_outer_rel,
										 RelOptInfo *fc_inner_rel,
										 PartitionMap *fc_outer_map,
										 PartitionMap *fc_inner_map,
										 int fc_nmerged,
										 List **fc_outer_parts,
										 List **fc_inner_parts);
static PartitionBoundInfo fc_build_merged_partition_bounds(char fc_strategy,
														List *fc_merged_datums,
														List *fc_merged_kinds,
														List *fc_merged_indexes,
														int fc_null_index,
														int fc_default_index);
static int	fc_get_range_partition(RelOptInfo *fc_rel,
								PartitionBoundInfo fc_bi,
								int *fc_lb_pos,
								PartitionRangeBound *fc_lb,
								PartitionRangeBound *fc_ub);
static int	fc_get_range_partition_internal(PartitionBoundInfo fc_bi,
										 int *fc_lb_pos,
										 PartitionRangeBound *fc_lb,
										 PartitionRangeBound *fc_ub);
static bool fc_compare_range_partitions(int fc_partnatts, FmgrInfo *fc_partsupfuncs,
									 Oid *fc_partcollations,
									 PartitionRangeBound *fc_outer_lb,
									 PartitionRangeBound *fc_outer_ub,
									 PartitionRangeBound *fc_inner_lb,
									 PartitionRangeBound *fc_inner_ub,
									 int *fc_lb_cmpval, int *fc_ub_cmpval);
static void fc_get_merged_range_bounds(int fc_partnatts, FmgrInfo *fc_partsupfuncs,
									Oid *fc_partcollations, JoinType fc_jointype,
									PartitionRangeBound *fc_outer_lb,
									PartitionRangeBound *fc_outer_ub,
									PartitionRangeBound *fc_inner_lb,
									PartitionRangeBound *fc_inner_ub,
									int fc_lb_cmpval, int fc_ub_cmpval,
									PartitionRangeBound *fc_merged_lb,
									PartitionRangeBound *fc_merged_ub);
static void fc_add_merged_range_bounds(int fc_partnatts, FmgrInfo *fc_partsupfuncs,
									Oid *fc_partcollations,
									PartitionRangeBound *fc_merged_lb,
									PartitionRangeBound *fc_merged_ub,
									int fc_merged_index,
									List **fc_merged_datums,
									List **fc_merged_kinds,
									List **fc_merged_indexes);
static PartitionRangeBound *fc_make_one_partition_rbound(PartitionKey fc_key, int fc_index,
													  List *fc_datums, bool fc_lower);
static int32 fc_partition_hbound_cmp(int fc_modulus1, int fc_remainder1, int fc_modulus2,
								  int fc_remainder2);
static int32 fc_partition_rbound_cmp(int fc_partnatts, FmgrInfo *fc_partsupfunc,
								  Oid *fc_partcollation, Datum *fc_datums1,
								  PartitionRangeDatumKind *fc_kind1, bool fc_lower1,
								  PartitionRangeBound *fc_b2);
static int	fc_partition_range_bsearch(int fc_partnatts, FmgrInfo *fc_partsupfunc,
									Oid *fc_partcollation,
									PartitionBoundInfo fc_boundinfo,
									PartitionRangeBound *fc_probe, int32 *fc_cmpval);
static Expr *fc_make_partition_op_expr(PartitionKey fc_key, int fc_keynum,
									uint16 fc_strategy, Expr *fc_arg1, Expr *fc_arg2);
static Oid	fc_get_partition_operator(PartitionKey fc_key, int fc_col,
								   StrategyNumber fc_strategy, bool *fc_need_relabel);
static List *fc_get_qual_for_hash(Relation fc_parent, PartitionBoundSpec *fc_spec);
static List *fc_get_qual_for_list(Relation fc_parent, PartitionBoundSpec *fc_spec);
static List *fc_get_qual_for_range(Relation fc_parent, PartitionBoundSpec *fc_spec,
								bool fc_for_default);
static void fc_get_range_key_properties(PartitionKey fc_key, int fc_keynum,
									 PartitionRangeDatum *fc_ldatum,
									 PartitionRangeDatum *fc_udatum,
									 ListCell **fc_partexprs_item,
									 Expr **fc_keyCol,
									 Const **fc_lower_val, Const **fc_upper_val);
static List *fc_get_range_nulltest(PartitionKey fc_key);

/*
 * get_qual_from_partbound
 *		给定一个用于分区边界的解析器节点，返回可执行的
 *		表达式列表作为分区约束
 */
List * get_qual_from_partbound(Relation fc_parent, PartitionBoundSpec *fc_spec)
{
	PartitionKey fc_key = RelationGetPartitionKey(fc_parent);
	List	   *fc_my_qual = NIL;

	Assert(fc_key != NULL);

	switch (fc_key->strategy)
	{
		case PARTITION_STRATEGY_HASH:
			Assert(fc_spec->strategy == PARTITION_STRATEGY_HASH);
			fc_my_qual = fc_get_qual_for_hash(fc_parent, fc_spec);
			break;

		case PARTITION_STRATEGY_LIST:
			Assert(fc_spec->strategy == PARTITION_STRATEGY_LIST);
			fc_my_qual = fc_get_qual_for_list(fc_parent, fc_spec);
			break;

		case PARTITION_STRATEGY_RANGE:
			Assert(fc_spec->strategy == PARTITION_STRATEGY_RANGE);
			fc_my_qual = fc_get_qual_for_range(fc_parent, fc_spec, false);
			break;

		default:
			elog(ERROR, "unexpected partition strategy: %d",
				 (int) fc_key->strategy);
	}

	return fc_my_qual;
}

/*
 *	partition_bounds_create
 *		从一系列PartitionBoundSpec
 *		节点构建一个PartitionBoundInfo结构
 *
 * 此函数创建一个PartitionBoundInfo并根据输入列表填充其
 * 各个成员的值。重要的是，'datums'数组将包含单个边界的
 * Datum表示（可能在范围边界的情况下经过去重），按照qsort_partition_*
 * 分区方法的函数定义的规范顺序排序。'indexes'数组将包含与边界数量相同数量的元素（此规则的特定例外
 * 在函数体中列出），这些元素表示分区的0基准规范位置。
 *
 * 从此函数返回时，*mapping被设置为一个包含
 * list_length(boundspecs)元素的数组，每个元素将
 * 分区的原始索引映射到其规范索引。
 *
 * 注意：此函数返回的对象完全分配在当前内存上下文中。
 */
PartitionBoundInfo partition_bounds_create(PartitionBoundSpec **fc_boundspecs, int fc_nparts,
						PartitionKey fc_key, int **fc_mapping)
{
	int			fc_i;

	Assert(fc_nparts > 0);

	/*
	 * 对于每种分区方法，我们首先将分区边界
	 * 从它们的解析器节点表示转换为内部表示，
	 * 以及任何其他预处理（例如去重范围
	 * 边界）。然后将结果边界数据添加到
	 * PartitionBoundInfo中的'datums'数组中。对于每个添加的数据，还会
	 * 将指示规范分区索引的整数添加到'indexes'数组中。
	 *
	 * 对于每个边界，我们记下其分区在原始列表中的位置（0基准），以便稍后将其映射到规范索引。
	 */

	/*
	 * 用无效值初始化映射数组，这在下面的每个子例程中根据边界类型填充。
	 */
	*fc_mapping = (int *) palloc(sizeof(int) * fc_nparts);
	for (fc_i = 0; fc_i < fc_nparts; fc_i++)
		(*fc_mapping)[fc_i] = -1;

	switch (fc_key->strategy)
	{
		case PARTITION_STRATEGY_HASH:
			return fc_create_hash_bounds(fc_boundspecs, fc_nparts, fc_key, fc_mapping);

		case PARTITION_STRATEGY_LIST:
			return fc_create_list_bounds(fc_boundspecs, fc_nparts, fc_key, fc_mapping);

		case PARTITION_STRATEGY_RANGE:
			return fc_create_range_bounds(fc_boundspecs, fc_nparts, fc_key, fc_mapping);

		default:
			elog(ERROR, "unexpected partition strategy: %d",
				 (int) fc_key->strategy);
			break;
	}

	Assert(false);
	return NULL;				/* 保持编译器安静 */
}

/*
 * create_hash_bounds
 *		为哈希分区表创建一个PartitionBoundInfo
 */
static PartitionBoundInfo fc_create_hash_bounds(PartitionBoundSpec **fc_boundspecs, int fc_nparts,
				   PartitionKey fc_key, int **fc_mapping)
{
	PartitionBoundInfo fc_boundinfo;
	PartitionHashBound *fc_hbounds;
	int			fc_i;
	int			fc_greatest_modulus;
	Datum	   *fc_boundDatums;

	fc_boundinfo = (PartitionBoundInfoData *)
		palloc0(sizeof(PartitionBoundInfoData));
	fc_boundinfo->strategy = fc_key->strategy;
	/* 没有特殊的哈希分区。 */
	fc_boundinfo->null_index = -1;
	fc_boundinfo->default_index = -1;

	fc_hbounds = (PartitionHashBound *)
		palloc(fc_nparts * sizeof(PartitionHashBound));

	/* 从节点转换为内部表示 */
	for (fc_i = 0; fc_i < fc_nparts; fc_i++)
	{
		PartitionBoundSpec *fc_spec = fc_boundspecs[fc_i];

		if (fc_spec->strategy != PARTITION_STRATEGY_HASH)
			elog(ERROR, "invalid strategy in partition bound spec");

		fc_hbounds[fc_i].modulus = fc_spec->modulus;
		fc_hbounds[fc_i].remainder = fc_spec->remainder;
		fc_hbounds[fc_i].index = fc_i;
	}

	/* 将所有边界按升序排序 */
	qsort(fc_hbounds, fc_nparts, sizeof(PartitionHashBound),
		  fc_qsort_partition_hbound_cmp);

	/* 排序后，模数现在以升序存储。 */
	fc_greatest_modulus = fc_hbounds[fc_nparts - 1].modulus;

	fc_boundinfo->ndatums = fc_nparts;
	fc_boundinfo->datums = (Datum **) palloc0(fc_nparts * sizeof(Datum *));
	fc_boundinfo->kind = NULL;
	fc_boundinfo->interleaved_parts = NULL;
	fc_boundinfo->nindexes = fc_greatest_modulus;
	fc_boundinfo->indexes = (int *) palloc(fc_greatest_modulus * sizeof(int));
	for (fc_i = 0; fc_i < fc_greatest_modulus; fc_i++)
		fc_boundinfo->indexes[fc_i] = -1;

	/*
	 * 在下面的循环中，为了避免分配一系列小的 datum
	 * 数组，这里我们只分配一个单一的数组，下面我们将
	 * 每个分区分配该数组的一部分。
	 */
	fc_boundDatums = (Datum *) palloc(fc_nparts * 2 * sizeof(Datum));

	/*
	 * 对于哈希分区，datum（模数和余数
	 * 对）数量和分区数量相同。索引简单地是从
	 * 0 到 (nparts - 1) 的值。
	 */
	for (fc_i = 0; fc_i < fc_nparts; fc_i++)
	{
		int			fc_modulus = fc_hbounds[fc_i].modulus;
		int			fc_remainder = fc_hbounds[fc_i].remainder;

		fc_boundinfo->datums[fc_i] = &fc_boundDatums[fc_i * 2];
		fc_boundinfo->datums[fc_i][0] = Int32GetDatum(fc_modulus);
		fc_boundinfo->datums[fc_i][1] = Int32GetDatum(fc_remainder);

		while (fc_remainder < fc_greatest_modulus)
		{
			/* 重叠？ */
			Assert(fc_boundinfo->indexes[fc_remainder] == -1);
			fc_boundinfo->indexes[fc_remainder] = fc_i;
			fc_remainder += fc_modulus;
		}

		(*fc_mapping)[fc_hbounds[fc_i].index] = fc_i;
	}
	pfree(fc_hbounds);

	return fc_boundinfo;
}

/*
 * get_non_null_list_datum_count
 * 		计算每个分区中非空 Datum 的数量。
 */
static int fc_get_non_null_list_datum_count(PartitionBoundSpec **fc_boundspecs, int fc_nparts)
{
	int			fc_i;
	int			fc_count = 0;

	for (fc_i = 0; fc_i < fc_nparts; fc_i++)
	{
		ListCell   *fc_lc;

		foreach(fc_lc, fc_boundspecs[fc_i]->listdatums)
		{
			Const	   *fc_val = lfirst_node(Const, fc_lc);

			if (!fc_val->constisnull)
				fc_count++;
		}
	}

	return fc_count;
}

/*
 * create_list_bounds
 *		为列表分区表创建一个 PartitionBoundInfo
 */
static PartitionBoundInfo fc_create_list_bounds(PartitionBoundSpec **fc_boundspecs, int fc_nparts,
				   PartitionKey fc_key, int **fc_mapping)
{
	PartitionBoundInfo fc_boundinfo;
	PartitionListValue *fc_all_values;
	int			fc_i;
	int			fc_j;
	int			fc_ndatums;
	int			fc_next_index = 0;
	int			fc_default_index = -1;
	int			fc_null_index = -1;
	Datum	   *fc_boundDatums;

	fc_boundinfo = (PartitionBoundInfoData *)
		palloc0(sizeof(PartitionBoundInfoData));
	fc_boundinfo->strategy = fc_key->strategy;
	/* 将在下面正确设置。 */
	fc_boundinfo->null_index = -1;
	fc_boundinfo->default_index = -1;

	fc_ndatums = fc_get_non_null_list_datum_count(fc_boundspecs, fc_nparts);
	fc_all_values = (PartitionListValue *)
		palloc(fc_ndatums * sizeof(PartitionListValue));

	/* 创建一个跨所有分区的非空值的统一列表。 */
	for (fc_j = 0, fc_i = 0; fc_i < fc_nparts; fc_i++)
	{
		PartitionBoundSpec *fc_spec = fc_boundspecs[fc_i];
		ListCell   *fc_c;

		if (fc_spec->strategy != PARTITION_STRATEGY_LIST)
			elog(ERROR, "invalid strategy in partition bound spec");

		/*
		 * 注意默认分区的分区边界规格的索引。
		 * 此分区没有要添加到非空 datum 列表中的 datum。
		 */
		if (fc_spec->is_default)
		{
			fc_default_index = fc_i;
			continue;
		}

		foreach(fc_c, fc_spec->listdatums)
		{
			Const	   *fc_val = lfirst_node(Const, fc_c);

			if (!fc_val->constisnull)
			{
				fc_all_values[fc_j].index = fc_i;
				fc_all_values[fc_j].value = fc_val->constvalue;
				fc_j++;
			}
			else
			{
				/*
				 * 永远不要将 null 放入值数组中；而是保存
				 * 存储 null 的分区的索引。
				 */
				if (fc_null_index != -1)
					elog(ERROR, "found null more than once");
				fc_null_index = fc_i;
			}
		}
	}

	/* 确保我们为 all_values 数组中的每个插槽找到了一个 Datum */
	Assert(fc_j == fc_ndatums);

	qsort_arg(fc_all_values, fc_ndatums, sizeof(PartitionListValue),
			  fc_qsort_partition_list_value_cmp, (void *) fc_key);

	fc_boundinfo->ndatums = fc_ndatums;
	fc_boundinfo->datums = (Datum **) palloc0(fc_ndatums * sizeof(Datum *));
	fc_boundinfo->kind = NULL;
	fc_boundinfo->interleaved_parts = NULL;
	fc_boundinfo->nindexes = fc_ndatums;
	fc_boundinfo->indexes = (int *) palloc(fc_ndatums * sizeof(int));

	/*
	 * 在下面的循环中，为了避免分配一系列小的 datum
	 * 数组，这里我们只分配一个单一的数组，下面我们将
	 * 为每个 datum 分配该数组的一部分。
	 */
	fc_boundDatums = (Datum *) palloc(fc_ndatums * sizeof(Datum));

	/*
	 * 复制值。标准索引是从 0 到 (nparts -
	 * 1) 的值，分配给每个分区，使得给定分区的所有 datum
	 * 接受相同值。给定分区的值是该分区在 all_values[] 数组中
	 * 的最小 datum 的索引。
	 */
	for (fc_i = 0; fc_i < fc_ndatums; fc_i++)
	{
		int			fc_orig_index = fc_all_values[fc_i].index;

		fc_boundinfo->datums[fc_i] = &fc_boundDatums[fc_i];
		fc_boundinfo->datums[fc_i][0] = datumCopy(fc_all_values[fc_i].value,
											fc_key->parttypbyval[0],
											fc_key->parttyplen[0]);

		/* 如果旧索引没有映射，分配一个 */
		if ((*fc_mapping)[fc_orig_index] == -1)
			(*fc_mapping)[fc_orig_index] = fc_next_index++;

		fc_boundinfo->indexes[fc_i] = (*fc_mapping)[fc_orig_index];
	}

	pfree(fc_all_values);

	/*
	 * 为 null_index 设置标准值（如果有）。
	 *
	 * 可能 null 接受分区尚未分配索引，这种情况可能发生
	 * 如果该分区仅接受 null，因此在上面的循环中没有处理
	 * 非空值。
	 */
	if (fc_null_index != -1)
	{
		Assert(fc_null_index >= 0);
		if ((*fc_mapping)[fc_null_index] == -1)
			(*fc_mapping)[fc_null_index] = fc_next_index++;
		fc_boundinfo->null_index = (*fc_mapping)[fc_null_index];
	}

	/* 为 default_index 设置标准值（如果有）。 */
	if (fc_default_index != -1)
	{
		/*
		 * 默认分区接受列表中未指定的任何值，
		 * 因此在分配非空 datum 的索引时不应映射索引。
		 */
		Assert(fc_default_index >= 0);
		Assert((*fc_mapping)[fc_default_index] == -1);
		(*fc_mapping)[fc_default_index] = fc_next_index++;
		fc_boundinfo->default_index = (*fc_mapping)[fc_default_index];
	}

	/*
	 * 计算交错分区。这里我们寻找可能与其他分区
	 * 交错的分区，并在 interleaved_parts 中设置一个位，
	 * 对于任何可能与另一个分区交错的分区。
	 */

	/*
	 * 进行交错分区必须有多个分区，
	 * 否则没有任何交错的内容。
	 */
	if (fc_nparts > 1)
	{
		/*
		 * 短路检查仅允许每个分区 1 个 Datum 的情况。
		 * 当这一点为真时，无需进行更昂贵的检查来寻找
		 * 交错值。
		 */
		if (fc_boundinfo->ndatums +
			partition_bound_accepts_nulls(fc_boundinfo) +
			partition_bound_has_default(fc_boundinfo) != fc_nparts)
		{
			int			fc_last_index = -1;

			/*
			 * 由于索引数组是按Datum顺序排序的，如果有任何
			 * 分区是交错的，那么分区索引将不会按升序排列。
			 * 在这里我们检查这一点，并记录所有不按顺序的分区。
			 */
			for (fc_i = 0; fc_i < fc_boundinfo->nindexes; fc_i++)
			{
				int			fc_index = fc_boundinfo->indexes[fc_i];

				if (fc_index < fc_last_index)
					fc_boundinfo->interleaved_parts = bms_add_member(fc_boundinfo->interleaved_parts,
																  fc_index);

				/*
				 * 另一方面，如果索引数组中存在null_index，
				 * 则NULL分区也必须允许一些其他Datum，
				 * 因此它是“交错的”。
				 */
				else if (partition_bound_accepts_nulls(fc_boundinfo) &&
						 fc_index == fc_boundinfo->null_index)
					fc_boundinfo->interleaved_parts = bms_add_member(fc_boundinfo->interleaved_parts,
																  fc_index);

				fc_last_index = fc_index;
			}
		}

		/*
		 * DEFAULT分区是“捕获所有”分区，可以包含
		 * 任何不属于其他任何分区的内容。如果存在
		 * 其他分区，则DEFAULT分区必须标记为
		 * 交错的。
		 */
		if (partition_bound_has_default(fc_boundinfo))
			fc_boundinfo->interleaved_parts = bms_add_member(fc_boundinfo->interleaved_parts,
														  fc_boundinfo->default_index);
	}


	/* 所有分区现在必须已分配规范索引。 */
	Assert(fc_next_index == fc_nparts);
	return fc_boundinfo;
}

/*
 * create_range_bounds
 *		为范围分区表创建PartitionBoundInfo
 */
static PartitionBoundInfo fc_create_range_bounds(PartitionBoundSpec **fc_boundspecs, int fc_nparts,
					PartitionKey fc_key, int **fc_mapping)
{
	PartitionBoundInfo fc_boundinfo;
	PartitionRangeBound **fc_rbounds = NULL;
	PartitionRangeBound **fc_all_bounds,
			   *fc_prev;
	int			fc_i,
				fc_k,
				fc_partnatts;
	int			fc_ndatums = 0;
	int			fc_default_index = -1;
	int			fc_next_index = 0;
	Datum	   *fc_boundDatums;
	PartitionRangeDatumKind *fc_boundKinds;

	fc_boundinfo = (PartitionBoundInfoData *)
		palloc0(sizeof(PartitionBoundInfoData));
	fc_boundinfo->strategy = fc_key->strategy;
	/* 没有特殊的接受NULL的范围分区。 */
	fc_boundinfo->null_index = -1;
	/* 将在下面正确设置。 */
	fc_boundinfo->default_index = -1;

	fc_all_bounds = (PartitionRangeBound **)
		palloc0(2 * fc_nparts * sizeof(PartitionRangeBound *));

	/* 创建所有分区的范围边界的统一列表。 */
	fc_ndatums = 0;
	for (fc_i = 0; fc_i < fc_nparts; fc_i++)
	{
		PartitionBoundSpec *fc_spec = fc_boundspecs[fc_i];
		PartitionRangeBound *fc_lower,
				   *fc_upper;

		if (fc_spec->strategy != PARTITION_STRATEGY_RANGE)
			elog(ERROR, "invalid strategy in partition bound spec");

		/*
		 * 注意默认分区的分区边界规范的索引。
		 * 对于这个分区，没有datum可以添加到all_bounds数组中。
		 */
		if (fc_spec->is_default)
		{
			fc_default_index = fc_i;
			continue;
		}

		fc_lower = fc_make_one_partition_rbound(fc_key, fc_i, fc_spec->lowerdatums, true);
		fc_upper = fc_make_one_partition_rbound(fc_key, fc_i, fc_spec->upperdatums, false);
		fc_all_bounds[fc_ndatums++] = fc_lower;
		fc_all_bounds[fc_ndatums++] = fc_upper;
	}

	Assert(fc_ndatums == fc_nparts * 2 ||
		   (fc_default_index != -1 && fc_ndatums == (fc_nparts - 1) * 2));

	/* 将所有边界按升序排序 */
	qsort_arg(fc_all_bounds, fc_ndatums,
			  sizeof(PartitionRangeBound *),
			  fc_qsort_partition_rbound_cmp,
			  (void *) fc_key);

	/* 将所有边界中的独特边界保存到rbounds中。 */
	fc_rbounds = (PartitionRangeBound **)
		palloc(fc_ndatums * sizeof(PartitionRangeBound *));
	fc_k = 0;
	fc_prev = NULL;
	for (fc_i = 0; fc_i < fc_ndatums; fc_i++)
	{
		PartitionRangeBound *fc_cur = fc_all_bounds[fc_i];
		bool		fc_is_distinct = false;
		int			fc_j;

		/* 当前边界是否不同于前一个边界？ */
		for (fc_j = 0; fc_j < fc_key->partnatts; fc_j++)
		{
			Datum		fc_cmpval;

			if (fc_prev == NULL || fc_cur->kind[fc_j] != fc_prev->kind[fc_j])
			{
				fc_is_distinct = true;
				break;
			}

			/*
			 * 如果两个边界都是MINVALUE或MAXVALUE，立即停止并将
			 * 它们视为相等，因为此点之后的任何值都必须被
			 * 忽略。
			 */
			if (fc_cur->kind[fc_j] != PARTITION_RANGE_DATUM_VALUE)
				break;

			fc_cmpval = FunctionCall2Coll(&fc_key->partsupfunc[fc_j],
									   fc_key->partcollation[fc_j],
									   fc_cur->datums[fc_j],
									   fc_prev->datums[fc_j]);
			if (DatumGetInt32(fc_cmpval) != 0)
			{
				fc_is_distinct = true;
				break;
			}
		}

		/*
		 * 只有当边界不同的时候，才将其保存到临时数组中，即
		 * rbounds，稍后将其复制到boundinfo datums数组中。
		 */
		if (fc_is_distinct)
			fc_rbounds[fc_k++] = fc_all_bounds[fc_i];

		fc_prev = fc_cur;
	}

	pfree(fc_all_bounds);

	/* 更新ndatums以保存唯一datum的计数。 */
	fc_ndatums = fc_k;

	/*
	 * 将datum添加到boundinfo。规范索引是从0开始到
	 * nparts - 1的值，按顺序分配给每个分区的上边界。
	 * 对于“datums”元素下限，它在“indexes”数组中为-1，以表示
	 * 对于小于如此边界且大于或等于前一个上边界的值没有分区存在。
	 */
	fc_boundinfo->ndatums = fc_ndatums;
	fc_boundinfo->datums = (Datum **) palloc0(fc_ndatums * sizeof(Datum *));
	fc_boundinfo->kind = (PartitionRangeDatumKind **)
		palloc(fc_ndatums *
			   sizeof(PartitionRangeDatumKind *));
	fc_boundinfo->interleaved_parts = NULL;

	/*
	 * 对于范围分区，额外的-1值作为indexes[]数组的最后元素存储。
	 */
	fc_boundinfo->nindexes = fc_ndatums + 1;
	fc_boundinfo->indexes = (int *) palloc((fc_ndatums + 1) * sizeof(int));

	/*
	 * 在下面的循环中，为了避免分配一系列小数组，
	 * 这里我们只为Datums分配一个单一数组，为
	 * PartitionRangeDatumKinds再分配一个，稍后我们将在每个循环中
	 * 仅分配这些数组的一部分。
	 */
	fc_partnatts = fc_key->partnatts;
	fc_boundDatums = (Datum *) palloc(fc_ndatums * fc_partnatts * sizeof(Datum));
	fc_boundKinds = (PartitionRangeDatumKind *) palloc(fc_ndatums * fc_partnatts *
													sizeof(PartitionRangeDatumKind));

	for (fc_i = 0; fc_i < fc_ndatums; fc_i++)
	{
		int			fc_j;

		fc_boundinfo->datums[fc_i] = &fc_boundDatums[fc_i * fc_partnatts];
		fc_boundinfo->kind[fc_i] = &fc_boundKinds[fc_i * fc_partnatts];
		for (fc_j = 0; fc_j < fc_partnatts; fc_j++)
		{
			if (fc_rbounds[fc_i]->kind[fc_j] == PARTITION_RANGE_DATUM_VALUE)
				fc_boundinfo->datums[fc_i][fc_j] =
					datumCopy(fc_rbounds[fc_i]->datums[fc_j],
							  fc_key->parttypbyval[fc_j],
							  fc_key->parttyplen[fc_j]);
			fc_boundinfo->kind[fc_i][fc_j] = fc_rbounds[fc_i]->kind[fc_j];
		}

		/*
		 * 对于无效索引没有映射。
		 *
		 * rbounds数组中的任何下边界都有无效的索引
		 * 分配，因为前一个边界（如果存在）和这个（下）边界之间的值
		 * 不属于任何现有分区的范围。
		 */
		if (fc_rbounds[fc_i]->lower)
			fc_boundinfo->indexes[fc_i] = -1;
		else
		{
			int			fc_orig_index = fc_rbounds[fc_i]->index;

			/* 如果旧索引没有映射，分配一个 */
			if ((*fc_mapping)[fc_orig_index] == -1)
				(*fc_mapping)[fc_orig_index] = fc_next_index++;

			fc_boundinfo->indexes[fc_i] = (*fc_mapping)[fc_orig_index];
		}
	}

	pfree(fc_rbounds);

	/* 为 default_index 设置标准值（如果有）。 */
	if (fc_default_index != -1)
	{
		Assert(fc_default_index >= 0 && (*fc_mapping)[fc_default_index] == -1);
		(*fc_mapping)[fc_default_index] = fc_next_index++;
		fc_boundinfo->default_index = (*fc_mapping)[fc_default_index];
	}

	/* 额外的-1元素。 */
	Assert(fc_i == fc_ndatums);
	fc_boundinfo->indexes[fc_i] = -1;

	/* 所有分区现在必须已分配规范索引。 */
	Assert(fc_next_index == fc_nparts);
	return fc_boundinfo;
}

/*
 * 两个分区界限集合在逻辑上相等吗？
 *
 * 用于 relcache.c 的保留逻辑（即，在 RelationClearRelation() 中）。
 * 这在 b1 和 b2 分别是两个独立关系的界限集合时也很有用，因为
 * PartitionBoundInfo 是分区界限的规范表示。
 */
bool partition_bounds_equal(int fc_partnatts, int16 *fc_parttyplen, bool *fc_parttypbyval,
					   PartitionBoundInfo fc_b1, PartitionBoundInfo fc_b2)
{
	int			fc_i;

	if (fc_b1->strategy != fc_b2->strategy)
		return false;

	if (fc_b1->ndatums != fc_b2->ndatums)
		return false;

	if (fc_b1->nindexes != fc_b2->nindexes)
		return false;

	if (fc_b1->null_index != fc_b2->null_index)
		return false;

	if (fc_b1->default_index != fc_b2->default_index)
		return false;

	/* 对于所有分区策略，indexes[] 数组必须匹配 */
	for (fc_i = 0; fc_i < fc_b1->nindexes; fc_i++)
	{
		if (fc_b1->indexes[fc_i] != fc_b2->indexes[fc_i])
			return false;
	}

	/* 最后，比较 datums[] 数组 */
	if (fc_b1->strategy == PARTITION_STRATEGY_HASH)
	{
		/*
		 * 我们将分区按其模数和余数的升序排列。
		 * 此外，每个模数都是下一个更大模数的因子。
		 * 因此，我们可以安全地将给定分区的索引存储在
		 * indexes 数组中该分区的余数处。此外，(余数 + N * 模数)
		 * 在 indexes 数组中的位置处的条目对于任何分区的
		 * （模数，余数）规范都是相同的。因此，从给定界限得到的
		 * datums 数组相同，当且仅当它们的 indexes 数组相同。
		 * 因此，比较 indexes 数组就足够了。
		 *
		 * 尽管如此，确保界限在索引匹配时确实相同。
		 * 哈希分区界限将模数和余数分别存储在
		 * b1->datums[i][0] 和 b1->datums[i][1] 位置。
		 */
#ifdef USE_ASSERT_CHECKING
		for (fc_i = 0; fc_i < fc_b1->ndatums; fc_i++)
			Assert((fc_b1->datums[fc_i][0] == fc_b2->datums[fc_i][0] &&
					fc_b1->datums[fc_i][1] == fc_b2->datums[fc_i][1]));
#endif
	}
	else
	{
		for (fc_i = 0; fc_i < fc_b1->ndatums; fc_i++)
		{
			int			fc_j;

			for (fc_j = 0; fc_j < fc_partnatts; fc_j++)
			{
				/* 对于范围分区，界限可能不是有限的。 */
				if (fc_b1->kind != NULL)
				{
					/* 不同的界限种类彼此不同 */
					if (fc_b1->kind[fc_i][fc_j] != fc_b2->kind[fc_i][fc_j])
						return false;

					/*
					 * 非有限界限在无需进一步检查的情况下相等。
					 */
					if (fc_b1->kind[fc_i][fc_j] != PARTITION_RANGE_DATUM_VALUE)
						continue;
				}

				/*
				 * 比较实际值。请注意，在这里调用从分区规范派生的
				 * 比较操作符既不正确也不安全。这将是不正确的，
				 * 因为我们希望在分区界限的任何更改时更新 relcache 条目，
				 * 而不仅仅是分区操作符认为重要的那些更改。
				 * 这将是不安全的，因为我们可能在一个已中止事务的上下文中
				 * 到达此代码，而任意的分区操作符在该上下文中可能不安全。
				 * datumIsEqual() 应该足够简单以保持安全。
				 */
				if (!datumIsEqual(fc_b1->datums[fc_i][fc_j], fc_b2->datums[fc_i][fc_j],
								  fc_parttypbyval[fc_j], fc_parttyplen[fc_j]))
					return false;
			}
		}
	}
	return true;
}

/*
 * 返回给定 PartitionBoundInfo 结构的副本。界限的数据类型
 * 由给定的分区键规范描述。
 *
 * 注意：重要的是，这个函数及其调用者不进行任何目录访问，
 * 也不进行任何其他会导致分配除返回的数据结构以外的内存的操作。
 * 因为这是在长期存在的上下文中调用的，这将导致不必要的内存泄漏。
 */
PartitionBoundInfo partition_bounds_copy(PartitionBoundInfo fc_src,
					  PartitionKey fc_key)
{
	PartitionBoundInfo fc_dest;
	int			fc_i;
	int			fc_ndatums;
	int			fc_nindexes;
	int			fc_partnatts;
	bool		fc_hash_part;
	int			fc_natts;
	Datum	   *fc_boundDatums;

	fc_dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));

	fc_dest->strategy = fc_src->strategy;
	fc_ndatums = fc_dest->ndatums = fc_src->ndatums;
	fc_nindexes = fc_dest->nindexes = fc_src->nindexes;
	fc_partnatts = fc_key->partnatts;

	/* 列分区表只有一个单一的分区键。 */
	Assert(fc_key->strategy != PARTITION_STRATEGY_LIST || fc_partnatts == 1);

	fc_dest->datums = (Datum **) palloc(sizeof(Datum *) * fc_ndatums);

	if (fc_src->kind != NULL)
	{
		PartitionRangeDatumKind *fc_boundKinds;

		/* 只有 RANGE 分区应该有非 NULL 类型 */
		Assert(fc_key->strategy == PARTITION_STRATEGY_RANGE);

		fc_dest->kind = (PartitionRangeDatumKind **) palloc(fc_ndatums *
														 sizeof(PartitionRangeDatumKind *));

		/*
		 * 在下面的循环中，为了避免为存储 PartitionRangeDatumKind
		 * 分配一系列小数组，我们在这里分配一个单一的块，
		 * 并为每个数据项使用它的较小部分。
		 */
		fc_boundKinds = (PartitionRangeDatumKind *) palloc(fc_ndatums * fc_partnatts *
														sizeof(PartitionRangeDatumKind));

		for (fc_i = 0; fc_i < fc_ndatums; fc_i++)
		{
			fc_dest->kind[fc_i] = &fc_boundKinds[fc_i * fc_partnatts];
			memcpy(fc_dest->kind[fc_i], fc_src->kind[fc_i],
				   sizeof(PartitionRangeDatumKind) * fc_partnatts);
		}
	}
	else
		fc_dest->kind = NULL;

	/* 复制分层的分区用于 LIST 分区表 */
	fc_dest->interleaved_parts = bms_copy(fc_src->interleaved_parts);

	/*
	 * 对于哈希分区，数据数组将有两个元素 - 模数和余数。
	 */
	fc_hash_part = (fc_key->strategy == PARTITION_STRATEGY_HASH);
	fc_natts = fc_hash_part ? 2 : fc_partnatts;
	fc_boundDatums = palloc(fc_ndatums * fc_natts * sizeof(Datum));

	for (fc_i = 0; fc_i < fc_ndatums; fc_i++)
	{
		int			fc_j;

		fc_dest->datums[fc_i] = &fc_boundDatums[fc_i * fc_natts];

		for (fc_j = 0; fc_j < fc_natts; fc_j++)
		{
			bool		fc_byval;
			int			fc_typlen;

			if (fc_hash_part)
			{
				fc_typlen = sizeof(int32); /* 总是 int4 */
				fc_byval = true;	/* int4 是按值传递 */
			}
			else
			{
				fc_byval = fc_key->parttypbyval[fc_j];
				fc_typlen = fc_key->parttyplen[fc_j];
			}

			if (fc_dest->kind == NULL ||
				fc_dest->kind[fc_i][fc_j] == PARTITION_RANGE_DATUM_VALUE)
				fc_dest->datums[fc_i][fc_j] = datumCopy(fc_src->datums[fc_i][fc_j],
											   fc_byval, fc_typlen);
		}
	}

	fc_dest->indexes = (int *) palloc(sizeof(int) * fc_nindexes);
	memcpy(fc_dest->indexes, fc_src->indexes, sizeof(int) * fc_nindexes);

	fc_dest->null_index = fc_src->null_index;
	fc_dest->default_index = fc_src->default_index;

	return fc_dest;
}

/*
 * partition_bounds_merge
 *		检查 'outer_rel' 的每个分区是否最多与 'inner_rel' 的一个分区匹配/重叠，反之亦然；如果是，则构建并返回
 *		这两个表之间的连接关系的分区边界，生成两个匹配/重叠分区的列表，这些列表分别返回给 *outer_parts 和 *inner_parts。
 *
 * 这些列表包含相同数量的分区，并且列表中相同位置的分区指示用于分区连接的连接对。
 * 如果一侧的分区与另一侧的多个分区匹配/重叠，则此函数返回 NULL，并将 *outer_parts 和 *inner_parts 设置为 NIL。
 */
PartitionBoundInfo partition_bounds_merge(int fc_partnatts,
					   FmgrInfo *fc_partsupfunc, Oid *fc_partcollation,
					   RelOptInfo *fc_outer_rel, RelOptInfo *fc_inner_rel,
					   JoinType fc_jointype,
					   List **fc_outer_parts, List **fc_inner_parts)
{
	/*
	 * 当前，此函数仅从 try_partitionwise_join() 被调用，
	 * 所以连接类型应该是 INNER、LEFT、FULL、SEMI 或 ANTI。
	 */
	Assert(fc_jointype == JOIN_INNER || fc_jointype == JOIN_LEFT ||
		   fc_jointype == JOIN_FULL || fc_jointype == JOIN_SEMI ||
		   fc_jointype == JOIN_ANTI);

	/* 分区策略应该相同。 */
	Assert(fc_outer_rel->boundinfo->strategy == fc_inner_rel->boundinfo->strategy);

	*fc_outer_parts = *fc_inner_parts = NIL;
	switch (fc_outer_rel->boundinfo->strategy)
	{
		case PARTITION_STRATEGY_HASH:

			/*
			 * 对于哈希分区表，我们当前仅支持在它们具有完全相同的分区边界时进行分区连接。
			 *
			 * XXX：可以考虑放宽限制以支持哈希分区表缺失分区和/或模数不同的情况，
			 * 但不清楚支持前一种情况是否有用，因为缺失分区是异常的。
			 * 另一方面，支持后一个情况是有用的，但在这种情况下，一侧的分区与另一侧的多个
			 * 分区匹配的可能性较高，而这是当前分区连接实现无法处理的场景。
			 */
			return NULL;

		case PARTITION_STRATEGY_LIST:
			return fc_merge_list_bounds(fc_partsupfunc,
									 fc_partcollation,
									 fc_outer_rel,
									 fc_inner_rel,
									 fc_jointype,
									 fc_outer_parts,
									 fc_inner_parts);

		case PARTITION_STRATEGY_RANGE:
			return fc_merge_range_bounds(fc_partnatts,
									  fc_partsupfunc,
									  fc_partcollation,
									  fc_outer_rel,
									  fc_inner_rel,
									  fc_jointype,
									  fc_outer_parts,
									  fc_inner_parts);

		default:
			elog(ERROR, "unexpected partition strategy: %d",
				 (int) fc_outer_rel->boundinfo->strategy);
			return NULL;		/* 保持编译器安静 */
	}
}

/*
 * merge_list_bounds
 *		为 LIST 分区表之间的连接关系创建分区边界，如果可能的话
 *
 * 在这个函数中，我们尝试通过比较存储在其分区边界中的列表值来查找来自两侧的匹配分区集。
 * 由于列表值按升序出现，因此使用类似于合并连接的算法来实现。如果一侧的分区没有在
 * 另一侧匹配的分区，算法会尝试与另一侧的默认分区匹配（如果存在）；如果没有，算法将尝试
 * 如果它位于外连接的非空侧，则与另一侧的虚拟分区匹配。此外，如果双方都有默认分区，
 * 算法会尝试彼此匹配。如果算法发现一个分区与另一侧的多个分区匹配，我们将放弃，
 * 这是当前分区连接实现无法处理的场景。
 */
static PartitionBoundInfo fc_merge_list_bounds(FmgrInfo *fc_partsupfunc, Oid *fc_partcollation,
				  RelOptInfo *fc_outer_rel, RelOptInfo *fc_inner_rel,
				  JoinType fc_jointype,
				  List **fc_outer_parts, List **fc_inner_parts)
{
	PartitionBoundInfo fc_merged_bounds = NULL;
	PartitionBoundInfo fc_outer_bi = fc_outer_rel->boundinfo;
	PartitionBoundInfo fc_inner_bi = fc_inner_rel->boundinfo;
	bool		fc_outer_has_default = partition_bound_has_default(fc_outer_bi);
	bool		fc_inner_has_default = partition_bound_has_default(fc_inner_bi);
	int			fc_outer_default = fc_outer_bi->default_index;
	int			fc_inner_default = fc_inner_bi->default_index;
	bool		fc_outer_has_null = partition_bound_accepts_nulls(fc_outer_bi);
	bool		fc_inner_has_null = partition_bound_accepts_nulls(fc_inner_bi);
	PartitionMap fc_outer_map;
	PartitionMap fc_inner_map;
	int			fc_outer_pos;
	int			fc_inner_pos;
	int			fc_next_index = 0;
	int			fc_null_index = -1;
	int			fc_default_index = -1;
	List	   *fc_merged_datums = NIL;
	List	   *fc_merged_indexes = NIL;

	Assert(*fc_outer_parts == NIL);
	Assert(*fc_inner_parts == NIL);
	Assert(fc_outer_bi->strategy == fc_inner_bi->strategy &&
		   fc_outer_bi->strategy == PARTITION_STRATEGY_LIST);
	/* 列分区不需要种类。 */
	Assert(!fc_outer_bi->kind && !fc_inner_bi->kind);

	fc_init_partition_map(fc_outer_rel, &fc_outer_map);
	fc_init_partition_map(fc_inner_rel, &fc_inner_map);

	/*
	 * 如果默认分区（如果有的话）被证明是空的，则认为它们
	 * 不存在。
	 */
	if (fc_outer_has_default && fc_is_dummy_partition(fc_outer_rel, fc_outer_default))
		fc_outer_has_default = false;
	if (fc_inner_has_default && fc_is_dummy_partition(fc_inner_rel, fc_inner_default))
		fc_inner_has_default = false;

	/*
	 * 从两侧合并分区。在每次迭代中，我们比较一对
	 * 列表值，一个来自每一侧，并决定相应的
	 * 分区是否匹配。如果两个值完全匹配，
	 * 则移动到下一对列表值，否则移动到
	 * 列表值较小的一侧的下一个列表值。
	 */
	fc_outer_pos = fc_inner_pos = 0;
	while (fc_outer_pos < fc_outer_bi->ndatums || fc_inner_pos < fc_inner_bi->ndatums)
	{
		int			fc_outer_index = -1;
		int			fc_inner_index = -1;
		Datum	   *fc_outer_datums;
		Datum	   *fc_inner_datums;
		int			fc_cmpval;
		Datum	   *fc_merged_datum = NULL;
		int			fc_merged_index = -1;

		if (fc_outer_pos < fc_outer_bi->ndatums)
		{
			/*
			 * 如果外侧的分区被证明是空的，
			 * 则忽略它并移动到外侧的下一个数据。
			 */
			fc_outer_index = fc_outer_bi->indexes[fc_outer_pos];
			if (fc_is_dummy_partition(fc_outer_rel, fc_outer_index))
			{
				fc_outer_pos++;
				continue;
			}
		}
		if (fc_inner_pos < fc_inner_bi->ndatums)
		{
			/*
			 * 如果内侧的分区被证明是空的，
			 * 则忽略它并移动到内侧的下一个数据。
			 */
			fc_inner_index = fc_inner_bi->indexes[fc_inner_pos];
			if (fc_is_dummy_partition(fc_inner_rel, fc_inner_index))
			{
				fc_inner_pos++;
				continue;
			}
		}

		/* 获取列表值。 */
		fc_outer_datums = fc_outer_pos < fc_outer_bi->ndatums ?
			fc_outer_bi->datums[fc_outer_pos] : NULL;
		fc_inner_datums = fc_inner_pos < fc_inner_bi->ndatums ?
			fc_inner_bi->datums[fc_inner_pos] : NULL;

		/*
		 * 我们运行这个循环直到两侧都完成。这使我们能够避免
		 * 重复代码来处理完成较晚一侧的剩余值。为此，
		 * 我们设置比较参数 cmpval，使其看起来
		 * 好像完成较早的一侧有一个比未完成一侧的任何
		 * 值都高的额外值。这样我们就推进
		 * 未完成一侧的值，直到它的所有值都用完。
		 */
		if (fc_outer_pos >= fc_outer_bi->ndatums)
			fc_cmpval = 1;
		else if (fc_inner_pos >= fc_inner_bi->ndatums)
			fc_cmpval = -1;
		else
		{
			Assert(fc_outer_datums != NULL && fc_inner_datums != NULL);
			fc_cmpval = DatumGetInt32(FunctionCall2Coll(&fc_partsupfunc[0],
													 fc_partcollation[0],
													 fc_outer_datums[0],
													 fc_inner_datums[0]));
		}

		if (fc_cmpval == 0)
		{
			/* 两个列表值完全匹配。 */
			Assert(fc_outer_pos < fc_outer_bi->ndatums);
			Assert(fc_inner_pos < fc_inner_bi->ndatums);
			Assert(fc_outer_index >= 0);
			Assert(fc_inner_index >= 0);

			/*
			 * 尝试合并两个分区。如果成功，则在下面添加合并分区的
			 * 列表值和索引。
			 */
			fc_merged_index = fc_merge_matching_partitions(&fc_outer_map, &fc_inner_map,
													 fc_outer_index, fc_inner_index,
													 &fc_next_index);
			if (fc_merged_index == -1)
				goto cleanup;

			fc_merged_datum = fc_outer_datums;

			/* 移动到下一对列表值。 */
			fc_outer_pos++;
			fc_inner_pos++;
		}
		else if (fc_cmpval < 0)
		{
			/* 内侧缺少一个列表值。 */
			Assert(fc_outer_pos < fc_outer_bi->ndatums);

			/*
			 * 如果内侧有默认分区，或者这是一个
			 * 外连接，尝试将合并的分区分配给外侧
			 * 分区（见 process_outer_partition()）。否则，外侧
			 * 分区将不对结果做贡献。
			 */
			if (fc_inner_has_default || IS_OUTER_JOIN(fc_jointype))
			{
				/* 获取外侧分区。 */
				fc_outer_index = fc_outer_bi->indexes[fc_outer_pos];
				Assert(fc_outer_index >= 0);
				fc_merged_index = fc_process_outer_partition(&fc_outer_map,
													   &fc_inner_map,
													   fc_outer_has_default,
													   fc_inner_has_default,
													   fc_outer_index,
													   fc_inner_default,
													   fc_jointype,
													   &fc_next_index,
													   &fc_default_index);
				if (fc_merged_index == -1)
					goto cleanup;
				fc_merged_datum = fc_outer_datums;
			}

			/* 移动到外侧的下一个列表值。 */
			fc_outer_pos++;
		}
		else
		{
			/* 外侧缺少一个列表值。 */
			Assert(fc_cmpval > 0);
			Assert(fc_inner_pos < fc_inner_bi->ndatums);

			/*
			 * 如果外侧有默认分区，或者这是一个 FULL
			 * 连接，尝试将合并的分区分配给内侧
			 * 分区（见 process_inner_partition()）。否则，内侧
			 * 分区将不对结果做贡献。
			 */
			if (fc_outer_has_default || fc_jointype == JOIN_FULL)
			{
				/* 获取内侧分区。 */
				fc_inner_index = fc_inner_bi->indexes[fc_inner_pos];
				Assert(fc_inner_index >= 0);
				fc_merged_index = fc_process_inner_partition(&fc_outer_map,
													   &fc_inner_map,
													   fc_outer_has_default,
													   fc_inner_has_default,
													   fc_inner_index,
													   fc_outer_default,
													   fc_jointype,
													   &fc_next_index,
													   &fc_default_index);
				if (fc_merged_index == -1)
					goto cleanup;
				fc_merged_datum = fc_inner_datums;
			}

			/* 移动到内侧的下一个列表值。 */
			fc_inner_pos++;
		}

		/*
		 * 如果我们分配了合并的分区，如果适当，添加合并分区的
		 * 列表值和索引。
		 */
		if (fc_merged_index >= 0 && fc_merged_index != fc_default_index)
		{
			fc_merged_datums = lappend(fc_merged_datums, fc_merged_datum);
			fc_merged_indexes = lappend_int(fc_merged_indexes, fc_merged_index);
		}
	}

	/*
	 * 如果 NULL 分区（如果有的话）被证明是空的，则认为它们
	 * 不存在。
	 */
	if (fc_outer_has_null &&
		fc_is_dummy_partition(fc_outer_rel, fc_outer_bi->null_index))
		fc_outer_has_null = false;
	if (fc_inner_has_null &&
		fc_is_dummy_partition(fc_inner_rel, fc_inner_bi->null_index))
		fc_inner_has_null = false;

	/* 合并 NULL 分区（如果有）。 */
	if (fc_outer_has_null || fc_inner_has_null)
		fc_merge_null_partitions(&fc_outer_map, &fc_inner_map,
							  fc_outer_has_null, fc_inner_has_null,
							  fc_outer_bi->null_index, fc_inner_bi->null_index,
							  fc_jointype, &fc_next_index, &fc_null_index);
	else
		Assert(fc_null_index == -1);

	/* 合并默认分区（如果有）。 */
	if (fc_outer_has_default || fc_inner_has_default)
		fc_merge_default_partitions(&fc_outer_map, &fc_inner_map,
								 fc_outer_has_default, fc_inner_has_default,
								 fc_outer_default, fc_inner_default,
								 fc_jointype, &fc_next_index, &fc_default_index);
	else
		Assert(fc_default_index == -1);

	/* 如果我们有合并的分区，创建分区边界。 */
	if (fc_next_index > 0)
	{
		/* 如果有必要，修复 merged_indexes 列表。 */
		if (fc_outer_map.did_remapping || fc_inner_map.did_remapping)
		{
			Assert(fc_jointype == JOIN_FULL);
			fc_fix_merged_indexes(&fc_outer_map, &fc_inner_map,
							   fc_next_index, fc_merged_indexes);
		}

		/* 使用映射匹配输入的分区。 */
		fc_generate_matching_part_pairs(fc_outer_rel, fc_inner_rel,
									 &fc_outer_map, &fc_inner_map,
									 fc_next_index,
									 fc_outer_parts, fc_inner_parts);
		Assert(*fc_outer_parts != NIL);
		Assert(*fc_inner_parts != NIL);
		Assert(list_length(*fc_outer_parts) == list_length(*fc_inner_parts));
		Assert(list_length(*fc_outer_parts) <= fc_next_index);

		/* 创建一个 PartitionBoundInfo 结构以返回。 */
		fc_merged_bounds = fc_build_merged_partition_bounds(fc_outer_bi->strategy,
													  fc_merged_datums,
													  NIL,
													  fc_merged_indexes,
													  fc_null_index,
													  fc_default_index);
		Assert(fc_merged_bounds);
	}

cleanup:
	/* 在返回之前释放本地内存。 */
	list_free(fc_merged_datums);
	list_free(fc_merged_indexes);
	fc_free_partition_map(&fc_outer_map);
	fc_free_partition_map(&fc_inner_map);

	return fc_merged_bounds;
}

/*
 * merge_range_bounds
 *		为范围分区表之间的连接关系创建分区边界（如果可能的话）
 *
 * 在这个函数中，我们试图通过比较存储在其分区边界中的范围，找到来自两侧的重叠分区集。由于范围以升序出现，因此采用类似于合并连接的算法进行处理。如果一侧的分区没有与另一侧的重叠分区，算法将尝试将其与另一侧的默认分区匹配（如果有）；如果没有，算法将尝试将其与另一侧的虚拟分区匹配（如果它位于外连接的非空侧）。此外，如果双方都有默认分区，算法会尝试将它们彼此匹配。如果算法发现一个分区与另一侧的多个分区重叠，则放弃，这是当前分区连接实现无法处理的情况。
 */
static PartitionBoundInfo fc_merge_range_bounds(int fc_partnatts, FmgrInfo *fc_partsupfuncs,
				   Oid *fc_partcollations,
				   RelOptInfo *fc_outer_rel, RelOptInfo *fc_inner_rel,
				   JoinType fc_jointype,
				   List **fc_outer_parts, List **fc_inner_parts)
{
	PartitionBoundInfo fc_merged_bounds = NULL;
	PartitionBoundInfo fc_outer_bi = fc_outer_rel->boundinfo;
	PartitionBoundInfo fc_inner_bi = fc_inner_rel->boundinfo;
	bool		fc_outer_has_default = partition_bound_has_default(fc_outer_bi);
	bool		fc_inner_has_default = partition_bound_has_default(fc_inner_bi);
	int			fc_outer_default = fc_outer_bi->default_index;
	int			fc_inner_default = fc_inner_bi->default_index;
	PartitionMap fc_outer_map;
	PartitionMap fc_inner_map;
	int			fc_outer_index;
	int			fc_inner_index;
	int			fc_outer_lb_pos;
	int			fc_inner_lb_pos;
	PartitionRangeBound fc_outer_lb;
	PartitionRangeBound fc_outer_ub;
	PartitionRangeBound fc_inner_lb;
	PartitionRangeBound fc_inner_ub;
	int			fc_next_index = 0;
	int			fc_default_index = -1;
	List	   *fc_merged_datums = NIL;
	List	   *fc_merged_kinds = NIL;
	List	   *fc_merged_indexes = NIL;

	Assert(*fc_outer_parts == NIL);
	Assert(*fc_inner_parts == NIL);
	Assert(fc_outer_bi->strategy == fc_inner_bi->strategy &&
		   fc_outer_bi->strategy == PARTITION_STRATEGY_RANGE);

	fc_init_partition_map(fc_outer_rel, &fc_outer_map);
	fc_init_partition_map(fc_inner_rel, &fc_inner_map);

	/*
	 * 如果默认分区（如果有的话）被证明是空的，则认为它们
	 * 不存在。
	 */
	if (fc_outer_has_default && fc_is_dummy_partition(fc_outer_rel, fc_outer_default))
		fc_outer_has_default = false;
	if (fc_inner_has_default && fc_is_dummy_partition(fc_inner_rel, fc_inner_default))
		fc_inner_has_default = false;

	/*
	 * 合并两侧的分区。在每次迭代中，我们比较一对范围，分别来自每一侧，并决定相应的分区是否匹配。如果两个范围重叠，则移动到下一对范围，否则移动到具有较低范围的一侧的下一个范围。outer_lb_pos/inner_lb_pos分别跟踪外部/内部 PartitionBoundInfos 中下界在数据数组中的位置。
	 */
	fc_outer_lb_pos = fc_inner_lb_pos = 0;
	fc_outer_index = fc_get_range_partition(fc_outer_rel, fc_outer_bi, &fc_outer_lb_pos,
									  &fc_outer_lb, &fc_outer_ub);
	fc_inner_index = fc_get_range_partition(fc_inner_rel, fc_inner_bi, &fc_inner_lb_pos,
									  &fc_inner_lb, &fc_inner_ub);
	while (fc_outer_index >= 0 || fc_inner_index >= 0)
	{
		bool		fc_overlap;
		int			fc_ub_cmpval;
		int			fc_lb_cmpval;
		PartitionRangeBound fc_merged_lb = {-1, NULL, NULL, true};
		PartitionRangeBound fc_merged_ub = {-1, NULL, NULL, false};
		int			fc_merged_index = -1;

		/*
		 * 我们运行这个循环直到双方都完成。这允许我们避免重复代码来处理稍后完成的一侧的剩余范围。为此，我们以这样的方式设置比较参数 cmpval，使早完成的一侧似乎有一个比未完成一侧的任何其他范围都高的额外范围。这样，我们可以推进未完成一侧的范围，直到其所有范围都耗尽。
		 */
		if (fc_outer_index == -1)
		{
			fc_overlap = false;
			fc_lb_cmpval = 1;
			fc_ub_cmpval = 1;
		}
		else if (fc_inner_index == -1)
		{
			fc_overlap = false;
			fc_lb_cmpval = -1;
			fc_ub_cmpval = -1;
		}
		else
			fc_overlap = fc_compare_range_partitions(fc_partnatts, fc_partsupfuncs,
											   fc_partcollations,
											   &fc_outer_lb, &fc_outer_ub,
											   &fc_inner_lb, &fc_inner_ub,
											   &fc_lb_cmpval, &fc_ub_cmpval);

		if (fc_overlap)
		{
			/* 两个范围重叠；形成一个连接对。 */

			PartitionRangeBound fc_save_outer_ub;
			PartitionRangeBound fc_save_inner_ub;

			/* 两个分区都不应已被合并。 */
			Assert(fc_outer_index >= 0);
			Assert(fc_outer_map.merged_indexes[fc_outer_index] == -1 &&
				   fc_outer_map.merged[fc_outer_index] == false);
			Assert(fc_inner_index >= 0);
			Assert(fc_inner_map.merged_indexes[fc_inner_index] == -1 &&
				   fc_inner_map.merged[fc_inner_index] == false);

			/*
			 * 获取合并分区的索引。两个分区还没有合并，所以分区应该成功合并。
			 */
			fc_merged_index = fc_merge_matching_partitions(&fc_outer_map, &fc_inner_map,
													 fc_outer_index, fc_inner_index,
													 &fc_next_index);
			Assert(fc_merged_index >= 0);

			/* 获取合并分区的范围边界。 */
			fc_get_merged_range_bounds(fc_partnatts, fc_partsupfuncs,
									fc_partcollations, fc_jointype,
									&fc_outer_lb, &fc_outer_ub,
									&fc_inner_lb, &fc_inner_ub,
									fc_lb_cmpval, fc_ub_cmpval,
									&fc_merged_lb, &fc_merged_ub);

			/* 保存两个分区的上界，以备后用。 */
			fc_save_outer_ub = fc_outer_ub;
			fc_save_inner_ub = fc_inner_ub;

			/* 移动到下一对范围。 */
			fc_outer_index = fc_get_range_partition(fc_outer_rel, fc_outer_bi, &fc_outer_lb_pos,
											  &fc_outer_lb, &fc_outer_ub);
			fc_inner_index = fc_get_range_partition(fc_inner_rel, fc_inner_bi, &fc_inner_lb_pos,
											  &fc_inner_lb, &fc_inner_ub);

			/*
			 * 如果一侧的分区范围与另一侧下一个分区的范围重叠，这将导致一侧的分区与另一侧至少两个分区匹配，这是我们当前不支持的分区连接的情况；放弃。
			 */
			if (fc_ub_cmpval > 0 && fc_inner_index >= 0 &&
				compare_range_bounds(fc_partnatts, fc_partsupfuncs, fc_partcollations,
									 &fc_save_outer_ub, &fc_inner_lb) > 0)
				goto cleanup;
			if (fc_ub_cmpval < 0 && fc_outer_index >= 0 &&
				compare_range_bounds(fc_partnatts, fc_partsupfuncs, fc_partcollations,
									 &fc_outer_lb, &fc_save_inner_ub) < 0)
				goto cleanup;

			/*
			 * 一行来自于一侧的非重叠部分（如果有的话）的分区可能
			 * 在另一侧找到其连接伙伴在默认分区（如果有的话），导致
			 * 与上述相同的情况；在这种情况下放弃。
			 */
			if ((fc_outer_has_default && (fc_lb_cmpval > 0 || fc_ub_cmpval < 0)) ||
				(fc_inner_has_default && (fc_lb_cmpval < 0 || fc_ub_cmpval > 0)))
				goto cleanup;
		}
		else if (fc_ub_cmpval < 0)
		{
			/* 一个非重叠的外部范围。 */

			/* 外部分区还不应被合并。 */
			Assert(fc_outer_index >= 0);
			Assert(fc_outer_map.merged_indexes[fc_outer_index] == -1 &&
				   fc_outer_map.merged[fc_outer_index] == false);

			/*
			 * 如果内侧有默认分区，或者这是一个
			 * 外连接，尝试将合并的分区分配给外侧
			 * 分区（见 process_outer_partition()）。否则，外侧
			 * 分区将不对结果做贡献。
			 */
			if (fc_inner_has_default || IS_OUTER_JOIN(fc_jointype))
			{
				fc_merged_index = fc_process_outer_partition(&fc_outer_map,
													   &fc_inner_map,
													   fc_outer_has_default,
													   fc_inner_has_default,
													   fc_outer_index,
													   fc_inner_default,
													   fc_jointype,
													   &fc_next_index,
													   &fc_default_index);
				if (fc_merged_index == -1)
					goto cleanup;
				fc_merged_lb = fc_outer_lb;
				fc_merged_ub = fc_outer_ub;
			}

			/* 移动到外侧的下一个范围。 */
			fc_outer_index = fc_get_range_partition(fc_outer_rel, fc_outer_bi, &fc_outer_lb_pos,
											  &fc_outer_lb, &fc_outer_ub);
		}
		else
		{
			/* 一个非重叠的内部范围。 */
			Assert(fc_ub_cmpval > 0);

			/* 内部分区还不应被合并。 */
			Assert(fc_inner_index >= 0);
			Assert(fc_inner_map.merged_indexes[fc_inner_index] == -1 &&
				   fc_inner_map.merged[fc_inner_index] == false);

			/*
			 * 如果外侧有默认分区，或者这是一个 FULL
			 * 连接，尝试将合并的分区分配给内侧
			 * 分区（见 process_inner_partition()）。否则，内侧
			 * 分区将不对结果做贡献。
			 */
			if (fc_outer_has_default || fc_jointype == JOIN_FULL)
			{
				fc_merged_index = fc_process_inner_partition(&fc_outer_map,
													   &fc_inner_map,
													   fc_outer_has_default,
													   fc_inner_has_default,
													   fc_inner_index,
													   fc_outer_default,
													   fc_jointype,
													   &fc_next_index,
													   &fc_default_index);
				if (fc_merged_index == -1)
					goto cleanup;
				fc_merged_lb = fc_inner_lb;
				fc_merged_ub = fc_inner_ub;
			}

			/* 移动到内侧的下一个范围。 */
			fc_inner_index = fc_get_range_partition(fc_inner_rel, fc_inner_bi, &fc_inner_lb_pos,
											  &fc_inner_lb, &fc_inner_ub);
		}

		/*
		 * 如果我们分配了合并的分区，在适当的情况下添加
		 * 合并分区的范围边界和索引。
		 */
		if (fc_merged_index >= 0 && fc_merged_index != fc_default_index)
			fc_add_merged_range_bounds(fc_partnatts, fc_partsupfuncs, fc_partcollations,
									&fc_merged_lb, &fc_merged_ub, fc_merged_index,
									&fc_merged_datums, &fc_merged_kinds,
									&fc_merged_indexes);
	}

	/* 合并默认分区（如果有）。 */
	if (fc_outer_has_default || fc_inner_has_default)
		fc_merge_default_partitions(&fc_outer_map, &fc_inner_map,
								 fc_outer_has_default, fc_inner_has_default,
								 fc_outer_default, fc_inner_default,
								 fc_jointype, &fc_next_index, &fc_default_index);
	else
		Assert(fc_default_index == -1);

	/* 如果我们有合并的分区，创建分区边界。 */
	if (fc_next_index > 0)
	{
		/*
		 * 与列表分区的情况不同，我们不会重新合并分区，
		 * 因此 did_remapping 应保持不变。
		 */
		Assert(!fc_outer_map.did_remapping);
		Assert(!fc_inner_map.did_remapping);

		/* 使用映射匹配输入的分区。 */
		fc_generate_matching_part_pairs(fc_outer_rel, fc_inner_rel,
									 &fc_outer_map, &fc_inner_map,
									 fc_next_index,
									 fc_outer_parts, fc_inner_parts);
		Assert(*fc_outer_parts != NIL);
		Assert(*fc_inner_parts != NIL);
		Assert(list_length(*fc_outer_parts) == list_length(*fc_inner_parts));
		Assert(list_length(*fc_outer_parts) == fc_next_index);

		/* 创建一个 PartitionBoundInfo 结构以返回。 */
		fc_merged_bounds = fc_build_merged_partition_bounds(fc_outer_bi->strategy,
													  fc_merged_datums,
													  fc_merged_kinds,
													  fc_merged_indexes,
													  -1,
													  fc_default_index);
		Assert(fc_merged_bounds);
	}

cleanup:
	/* 在返回之前释放本地内存。 */
	list_free(fc_merged_datums);
	list_free(fc_merged_kinds);
	list_free(fc_merged_indexes);
	fc_free_partition_map(&fc_outer_map);
	fc_free_partition_map(&fc_inner_map);

	return fc_merged_bounds;
}

/*
 * init_partition_map
 *		初始化给定关系的 PartitionMap 结构
 */
static void fc_init_partition_map(RelOptInfo *fc_rel, PartitionMap *fc_map)
{
	int			fc_nparts = fc_rel->nparts;
	int			fc_i;

	fc_map->nparts = fc_nparts;
	fc_map->merged_indexes = (int *) palloc(sizeof(int) * fc_nparts);
	fc_map->merged = (bool *) palloc(sizeof(bool) * fc_nparts);
	fc_map->did_remapping = false;
	fc_map->old_indexes = (int *) palloc(sizeof(int) * fc_nparts);
	for (fc_i = 0; fc_i < fc_nparts; fc_i++)
	{
		fc_map->merged_indexes[fc_i] = fc_map->old_indexes[fc_i] = -1;
		fc_map->merged[fc_i] = false;
	}
}

/*
 * free_partition_map
 */
static void fc_free_partition_map(PartitionMap *fc_map)
{
	pfree(fc_map->merged_indexes);
	pfree(fc_map->merged);
	pfree(fc_map->old_indexes);
}

/*
 * is_dummy_partition --- 是否证明分区为空？
 */
static bool fc_is_dummy_partition(RelOptInfo *fc_rel, int fc_part_index)
{
	RelOptInfo *fc_part_rel;

	Assert(fc_part_index >= 0);
	fc_part_rel = fc_rel->part_rels[fc_part_index];
	if (fc_part_rel == NULL || IS_DUMMY_REL(fc_part_rel))
		return true;
	return false;
}

/*
 * merge_matching_partitions
 *		尝试合并给定的外部/内部分区，如果成功则返回
 *		合并分区的索引，否则返回 -1
 *
 * 如果合并的分区是新创建的，*next_index 会递增。
 */
static int fc_merge_matching_partitions(PartitionMap *fc_outer_map, PartitionMap *fc_inner_map,
						  int fc_outer_index, int fc_inner_index, int *fc_next_index)
{
	int			fc_outer_merged_index;
	int			fc_inner_merged_index;
	bool		fc_outer_merged;
	bool		fc_inner_merged;

	Assert(fc_outer_index >= 0 && fc_outer_index < fc_outer_map->nparts);
	fc_outer_merged_index = fc_outer_map->merged_indexes[fc_outer_index];
	fc_outer_merged = fc_outer_map->merged[fc_outer_index];
	Assert(fc_inner_index >= 0 && fc_inner_index < fc_inner_map->nparts);
	fc_inner_merged_index = fc_inner_map->merged_indexes[fc_inner_index];
	fc_inner_merged = fc_inner_map->merged[fc_inner_index];

	/*
	 * 处理我们已经将合并分区分配给每个给定分区的情况。
	 */
	if (fc_outer_merged_index >= 0 && fc_inner_merged_index >= 0)
	{
		/*
		 * 如果合并的分区相同，则无需做任何事情；
		 * 返回合并分区的索引。否则，如果每个给定分区
		 * 都已与另一侧的虚拟分区合并，则将它们重新映射
		 * 到两个合并分区中的任意一个。否则，它们无法合并，
		 * 所以返回 -1。
		 */
		if (fc_outer_merged_index == fc_inner_merged_index)
		{
			Assert(fc_outer_merged);
			Assert(fc_inner_merged);
			return fc_outer_merged_index;
		}
		if (!fc_outer_merged && !fc_inner_merged)
		{
			/*
			 * 这只能发生在列表分区的情况下。我们将它们重新映射
			 * 到合并的分区，其索引较小，以保持列表分区的标准顺序
			 * 由分配给每个分区最小列表值的索引决定的属性。
			 */
			if (fc_outer_merged_index < fc_inner_merged_index)
			{
				fc_outer_map->merged[fc_outer_index] = true;
				fc_inner_map->merged_indexes[fc_inner_index] = fc_outer_merged_index;
				fc_inner_map->merged[fc_inner_index] = true;
				fc_inner_map->did_remapping = true;
				fc_inner_map->old_indexes[fc_inner_index] = fc_inner_merged_index;
				return fc_outer_merged_index;
			}
			else
			{
				fc_inner_map->merged[fc_inner_index] = true;
				fc_outer_map->merged_indexes[fc_outer_index] = fc_inner_merged_index;
				fc_outer_map->merged[fc_outer_index] = true;
				fc_outer_map->did_remapping = true;
				fc_outer_map->old_indexes[fc_outer_index] = fc_outer_merged_index;
				return fc_inner_merged_index;
			}
		}
		return -1;
	}

	/* 至少有一个给定的分区还不应被合并。 */
	Assert(fc_outer_merged_index == -1 || fc_inner_merged_index == -1);

	/*
	 * 如果它们都没有被合并，则合并它们。否则，如果其中一个
	 * 已经与另一侧的虚拟分区合并（而另一个还没有与任何东西
	 * 合并），则重新合并它们。否则，它们无法合并，因此返回 -1。
	 */
	if (fc_outer_merged_index == -1 && fc_inner_merged_index == -1)
	{
		int			fc_merged_index = *fc_next_index;

		Assert(!fc_outer_merged);
		Assert(!fc_inner_merged);
		fc_outer_map->merged_indexes[fc_outer_index] = fc_merged_index;
		fc_outer_map->merged[fc_outer_index] = true;
		fc_inner_map->merged_indexes[fc_inner_index] = fc_merged_index;
		fc_inner_map->merged[fc_inner_index] = true;
		*fc_next_index = *fc_next_index + 1;
		return fc_merged_index;
	}
	if (fc_outer_merged_index >= 0 && !fc_outer_map->merged[fc_outer_index])
	{
		Assert(fc_inner_merged_index == -1);
		Assert(!fc_inner_merged);
		fc_inner_map->merged_indexes[fc_inner_index] = fc_outer_merged_index;
		fc_inner_map->merged[fc_inner_index] = true;
		fc_outer_map->merged[fc_outer_index] = true;
		return fc_outer_merged_index;
	}
	if (fc_inner_merged_index >= 0 && !fc_inner_map->merged[fc_inner_index])
	{
		Assert(fc_outer_merged_index == -1);
		Assert(!fc_outer_merged);
		fc_outer_map->merged_indexes[fc_outer_index] = fc_inner_merged_index;
		fc_outer_map->merged[fc_outer_index] = true;
		fc_inner_map->merged[fc_inner_index] = true;
		return fc_inner_merged_index;
	}
	return -1;
}

/*
 * process_outer_partition
 *		尝试为给定的外部分区分配一个合并分区，如果成功，
 *		则返回合并分区的索引，否则返回 -1
 *
 * 如果分区是新创建的，*next_index 会递增。此外，如果它
 * 是连接关系的默认分区，则如果尚未完成，将 *default_index 
 * 设置为索引。
 */
static int fc_process_outer_partition(PartitionMap *fc_outer_map,
						PartitionMap *fc_inner_map,
						bool fc_outer_has_default,
						bool fc_inner_has_default,
						int fc_outer_index,
						int fc_inner_default,
						JoinType fc_jointype,
						int *fc_next_index,
						int *fc_default_index)
{
	int			fc_merged_index = -1;

	Assert(fc_outer_index >= 0);

	/*
	 * 如果内部侧有默认分区，来自外部
	 * 分区的行可能会在默认分区中找到其连接伙伴；尝试
	 * 将外部分区与默认分区合并。否则，
	 * 这应该是外部连接，在这种情况下，外部分区必须
	 * 全部扫描；将外部分区与另一侧的虚拟
	 * 分区合并。
	 */
	if (fc_inner_has_default)
	{
		Assert(fc_inner_default >= 0);

		/*
		 * 如果外部侧也有默认分区，则内部侧的默认
		 * 分区将在另一侧有两个匹配的分区：外部分区
		 * 和外部侧的默认分区。基于分区的连接尚未处理
		 * 这种情况。
		 */
		if (fc_outer_has_default)
			return -1;

		fc_merged_index = fc_merge_matching_partitions(fc_outer_map, fc_inner_map,
												 fc_outer_index, fc_inner_default,
												 fc_next_index);
		if (fc_merged_index == -1)
			return -1;

		/*
		 * 如果这是一个 FULL 连接，内部侧的默认分区
		 * 无论如何都必须全部扫描，因此结果分区将
		 * 包含默认分区中的所有键值，而连接关系的任何其他
		 * 分区都不会包含。因此，结果分区将作为
		 * 连接关系的默认分区；如果尚未完成，则将索引
		 * 记录在 *default_index 中。
		 */
		if (fc_jointype == JOIN_FULL)
		{
			if (*fc_default_index == -1)
				*fc_default_index = fc_merged_index;
			else
				Assert(*fc_default_index == fc_merged_index);
		}
	}
	else
	{
		Assert(IS_OUTER_JOIN(fc_jointype));
		Assert(fc_jointype != JOIN_RIGHT);

		/* 如果我们已经分配了分区，则无需做任何操作。 */
		fc_merged_index = fc_outer_map->merged_indexes[fc_outer_index];
		if (fc_merged_index == -1)
			fc_merged_index = fc_merge_partition_with_dummy(fc_outer_map, fc_outer_index,
													  fc_next_index);
	}
	return fc_merged_index;
}

/*
 * process_inner_partition
 *		尝试为给定的内部分区分配一个合并分区，如果成功，
 *		则返回合并分区的索引，否则返回 -1
 *
 * 如果分区是新创建的，*next_index 会递增。此外，如果它
 * 是连接关系的默认分区，则如果尚未完成，将 *default_index 
 * 设置为索引。
 */
static int fc_process_inner_partition(PartitionMap *fc_outer_map,
						PartitionMap *fc_inner_map,
						bool fc_outer_has_default,
						bool fc_inner_has_default,
						int fc_inner_index,
						int fc_outer_default,
						JoinType fc_jointype,
						int *fc_next_index,
						int *fc_default_index)
{
	int			fc_merged_index = -1;

	Assert(fc_inner_index >= 0);

	/*
	 * 如果外部侧有默认分区，来自内部
	 * 分区的行可能会在默认分区中找到其连接伙伴；尝试
	 * 将内部分区与默认分区合并。否则，
	 * 这应该是一个 FULL 连接，在这种情况下内部分区必须
	 * 全部扫描；将内部分区与另一侧的虚拟
	 * 分区合并。
	 */
	if (fc_outer_has_default)
	{
		Assert(fc_outer_default >= 0);

		/*
		 * 如果内部侧也有默认分区，则外部侧的默认
		 * 分区将在另一侧有两个匹配的分区：内部分区
		 * 和内部侧的默认分区。基于分区的连接尚未处理
		 * 这种情况。
		 */
		if (fc_inner_has_default)
			return -1;

		fc_merged_index = fc_merge_matching_partitions(fc_outer_map, fc_inner_map,
												 fc_outer_default, fc_inner_index,
												 fc_next_index);
		if (fc_merged_index == -1)
			return -1;

		/*
		 * 如果这是一个外连接，外侧的默认分区
		 * 无论如何都必须全部扫描，因此得到的分区
		 * 将包含来自默认分区的所有键值，而任何
		 * 连接关系的其他分区将不包含。 因此，结果分区
		 * 将充当连接关系的默认分区；如果尚未完成，
		 * 请在 *default_index 中记录索引。
		 */
		if (IS_OUTER_JOIN(fc_jointype))
		{
			Assert(fc_jointype != JOIN_RIGHT);
			if (*fc_default_index == -1)
				*fc_default_index = fc_merged_index;
			else
				Assert(*fc_default_index == fc_merged_index);
		}
	}
	else
	{
		Assert(fc_jointype == JOIN_FULL);

		/* 如果我们已经分配了分区，则无需做任何操作。 */
		fc_merged_index = fc_inner_map->merged_indexes[fc_inner_index];
		if (fc_merged_index == -1)
			fc_merged_index = fc_merge_partition_with_dummy(fc_inner_map, fc_inner_index,
													  fc_next_index);
	}
	return fc_merged_index;
}

/*
 * merge_null_partitions
 *		合并连接的外侧和内侧的 NULL 分区。
 *
 * 如果由它们生成的合并分区是连接
 * 关系的 NULL 分区，则 *null_index 被设置为合并分区的索引。
 *
 * 注意：我们在这里假设分区连接的连接子句是严格的，
 * 因为 have_partkey_equi_join() 要求相应的操作符
 * 可合并连接，而我们目前假设可合并连接操作符是
 * 严格的（参见 MJEvalOuterValues()/MJEvalInnerValues()）。
 */
static void fc_merge_null_partitions(PartitionMap *fc_outer_map,
					  PartitionMap *fc_inner_map,
					  bool fc_outer_has_null,
					  bool fc_inner_has_null,
					  int fc_outer_null,
					  int fc_inner_null,
					  JoinType fc_jointype,
					  int *fc_next_index,
					  int *fc_null_index)
{
	bool		fc_consider_outer_null = false;
	bool		fc_consider_inner_null = false;

	Assert(fc_outer_has_null || fc_inner_has_null);
	Assert(*fc_null_index == -1);

	/*
	 * 检查 NULL 分区是否已经合并，如果是，
	 * 设置 consider_outer_null/consider_inner_null 标志。
	 */
	if (fc_outer_has_null)
	{
		Assert(fc_outer_null >= 0 && fc_outer_null < fc_outer_map->nparts);
		if (fc_outer_map->merged_indexes[fc_outer_null] == -1)
			fc_consider_outer_null = true;
	}
	if (fc_inner_has_null)
	{
		Assert(fc_inner_null >= 0 && fc_inner_null < fc_inner_map->nparts);
		if (fc_inner_map->merged_indexes[fc_inner_null] == -1)
			fc_consider_inner_null = true;
	}

	/* 如果两个标志都设置为 false，我们无需做任何事情。 */
	if (!fc_consider_outer_null && !fc_consider_inner_null)
		return;

	if (fc_consider_outer_null && !fc_consider_inner_null)
	{
		Assert(fc_outer_has_null);

		/*
		 * 如果这是一个外连接，外侧的 NULL 分区
		 * 无论如何都必须扫描全部；将 NULL 分区与
		 * 另一侧的虚拟分区合并。 在这种情况下，
		 * consider_outer_null 表示 NULL 分区仅包含
		 * NULL 值作为键值，因此合并分区也将如此；
		 * 将其视为连接关系的 NULL 分区。
		 */
		if (IS_OUTER_JOIN(fc_jointype))
		{
			Assert(fc_jointype != JOIN_RIGHT);
			*fc_null_index = fc_merge_partition_with_dummy(fc_outer_map, fc_outer_null,
													 fc_next_index);
		}
	}
	else if (!fc_consider_outer_null && fc_consider_inner_null)
	{
		Assert(fc_inner_has_null);

		/*
		 * 如果这是一个 FULL 连接，内侧的 NULL 分区
		 * 无论如何都必须扫描全部；将 NULL 分区与
		 * 另一侧的虚拟分区合并。 在这种情况下，
		 * consider_inner_null 表示 NULL 分区仅包含
		 * NULL 值作为键值，因此合并分区也将如此；
		 * 将其视为连接关系的 NULL 分区。
		 */
		if (fc_jointype == JOIN_FULL)
			*fc_null_index = fc_merge_partition_with_dummy(fc_inner_map, fc_inner_null,
													 fc_next_index);
	}
	else
	{
		Assert(fc_consider_outer_null && fc_consider_inner_null);
		Assert(fc_outer_has_null);
		Assert(fc_inner_has_null);

		/*
		 * 如果这是一个外连接，外侧的 NULL 分区（以及
		 * 如果这是一个 FULL 连接则内侧的 NULL 分区）都
		 * 无论如何都必须扫描全部，因此将它们合并。 
		 * 请注意，每个 NULL 分区尚未合并，因此
		 * 应该成功合并。 类似于上述，每个 NULL
		 * 分区仅包含 NULL 值作为键值，因此合并分区也将如此；
		 * 将其视为连接关系的 NULL 分区。
		 *
		 * 注意：如果这是一个 INNER/SEMI 连接，则连接子句永远不会
		 * 被两个 NULL 值满足（参见上面的评论），因此两个 NULL
		 * 分区可以被消除。
		 */
		if (IS_OUTER_JOIN(fc_jointype))
		{
			Assert(fc_jointype != JOIN_RIGHT);
			*fc_null_index = fc_merge_matching_partitions(fc_outer_map, fc_inner_map,
													fc_outer_null, fc_inner_null,
													fc_next_index);
			Assert(*fc_null_index >= 0);
		}
	}
}

/*
 * merge_default_partitions
 *		合并连接的外侧和内侧的默认分区。
 *
 * 如果由它们生成的合并分区是连接关系的默认分区，*default_index 将被设置为合并分区的索引。
 */
static void fc_merge_default_partitions(PartitionMap *fc_outer_map,
						 PartitionMap *fc_inner_map,
						 bool fc_outer_has_default,
						 bool fc_inner_has_default,
						 int fc_outer_default,
						 int fc_inner_default,
						 JoinType fc_jointype,
						 int *fc_next_index,
						 int *fc_default_index)
{
	int			fc_outer_merged_index = -1;
	int			fc_inner_merged_index = -1;

	Assert(fc_outer_has_default || fc_inner_has_default);

	/* 获取默认分区的合并分区索引。 */
	if (fc_outer_has_default)
	{
		Assert(fc_outer_default >= 0 && fc_outer_default < fc_outer_map->nparts);
		fc_outer_merged_index = fc_outer_map->merged_indexes[fc_outer_default];
	}
	if (fc_inner_has_default)
	{
		Assert(fc_inner_default >= 0 && fc_inner_default < fc_inner_map->nparts);
		fc_inner_merged_index = fc_inner_map->merged_indexes[fc_inner_default];
	}

	if (fc_outer_has_default && !fc_inner_has_default)
	{
		/*
		 * 如果这是外连接，外侧的默认分区必须完全扫描；如果我们尚未分配分区，
		 * 将默认分区与另一侧的虚拟分区合并。合并的分区将作为连接关系的默认
		 * 分区（参见 process_inner_partition() 中的注释）。
		 */
		if (IS_OUTER_JOIN(fc_jointype))
		{
			Assert(fc_jointype != JOIN_RIGHT);
			if (fc_outer_merged_index == -1)
			{
				Assert(*fc_default_index == -1);
				*fc_default_index = fc_merge_partition_with_dummy(fc_outer_map,
															fc_outer_default,
															fc_next_index);
			}
			else
				Assert(*fc_default_index == fc_outer_merged_index);
		}
		else
			Assert(*fc_default_index == -1);
	}
	else if (!fc_outer_has_default && fc_inner_has_default)
	{
		/*
		 * 如果这是 FULL 连接，内侧的默认分区必须完全扫描；如果我们尚未分配
		 * 分区，将默认分区与另一侧的虚拟分区合并。合并的分区将作为连接关系
		 * 的默认分区（参见 process_outer_partition() 中的注释）。
		 */
		if (fc_jointype == JOIN_FULL)
		{
			if (fc_inner_merged_index == -1)
			{
				Assert(*fc_default_index == -1);
				*fc_default_index = fc_merge_partition_with_dummy(fc_inner_map,
															fc_inner_default,
															fc_next_index);
			}
			else
				Assert(*fc_default_index == fc_inner_merged_index);
		}
		else
			Assert(*fc_default_index == -1);
	}
	else
	{
		Assert(fc_outer_has_default && fc_inner_has_default);

		/*
		 * 默认分区必须彼此连接，因此合并它们。请注意，每个默认分区尚未合并
		 * (参见 process_outer_partition()/process_inner_partition())，因此它们应
		 * 该成功合并。合并的分区将作为连接关系的默认分区。
		 */
		Assert(fc_outer_merged_index == -1);
		Assert(fc_inner_merged_index == -1);
		Assert(*fc_default_index == -1);
		*fc_default_index = fc_merge_matching_partitions(fc_outer_map,
												   fc_inner_map,
												   fc_outer_default,
												   fc_inner_default,
												   fc_next_index);
		Assert(*fc_default_index >= 0);
	}
}

/*
 * merge_partition_with_dummy
 *		为给定的分区分配一个连接关系的新分区
 *
 * 注意：调用者假设给定分区在另一侧没有非虚拟的匹配分区，但如果给定分区
 * 后来找到了匹配分区，我们将调整分配。
 */
static int fc_merge_partition_with_dummy(PartitionMap *fc_map, int fc_index, int *fc_next_index)
{
	int			fc_merged_index = *fc_next_index;

	Assert(fc_index >= 0 && fc_index < fc_map->nparts);
	Assert(fc_map->merged_indexes[fc_index] == -1);
	Assert(!fc_map->merged[fc_index]);
	fc_map->merged_indexes[fc_index] = fc_merged_index;
	/* 保持合并标志不变！ */
	*fc_next_index = *fc_next_index + 1;
	return fc_merged_index;
}

/*
 * fix_merged_indexes
 *		调整重新合并分区的合并索引
 */
static void fc_fix_merged_indexes(PartitionMap *fc_outer_map, PartitionMap *fc_inner_map,
				   int fc_nmerged, List *fc_merged_indexes)
{
	int		   *fc_new_indexes;
	int			fc_merged_index;
	int			fc_i;
	ListCell   *fc_lc;

	Assert(fc_nmerged > 0);

	fc_new_indexes = (int *) palloc(sizeof(int) * fc_nmerged);
	for (fc_i = 0; fc_i < fc_nmerged; fc_i++)
		fc_new_indexes[fc_i] = -1;

	/* 使用映射构建旧合并索引到新合并索引的映射。 */
	if (fc_outer_map->did_remapping)
	{
		for (fc_i = 0; fc_i < fc_outer_map->nparts; fc_i++)
		{
			fc_merged_index = fc_outer_map->old_indexes[fc_i];
			if (fc_merged_index >= 0)
				fc_new_indexes[fc_merged_index] = fc_outer_map->merged_indexes[fc_i];
		}
	}
	if (fc_inner_map->did_remapping)
	{
		for (fc_i = 0; fc_i < fc_inner_map->nparts; fc_i++)
		{
			fc_merged_index = fc_inner_map->old_indexes[fc_i];
			if (fc_merged_index >= 0)
				fc_new_indexes[fc_merged_index] = fc_inner_map->merged_indexes[fc_i];
		}
	}

	/* 使用映射修复 merged_indexes 列表。 */
	foreach(fc_lc, fc_merged_indexes)
	{
		fc_merged_index = lfirst_int(fc_lc);
		Assert(fc_merged_index >= 0);
		if (fc_new_indexes[fc_merged_index] >= 0)
			lfirst_int(fc_lc) = fc_new_indexes[fc_merged_index];
	}

	pfree(fc_new_indexes);
}

/*
 * generate_matching_part_pairs
 *		生成一对生成合并分区的分区列表
 *
 * 分区列表按合并分区索引的顺序构建，并返回在 *outer_parts 和
 * *inner_parts 中。
 */
static void fc_generate_matching_part_pairs(RelOptInfo *fc_outer_rel, RelOptInfo *fc_inner_rel,
							 PartitionMap *fc_outer_map, PartitionMap *fc_inner_map,
							 int fc_nmerged,
							 List **fc_outer_parts, List **fc_inner_parts)
{
	int			fc_outer_nparts = fc_outer_map->nparts;
	int			fc_inner_nparts = fc_inner_map->nparts;
	int		   *fc_outer_indexes;
	int		   *fc_inner_indexes;
	int			fc_max_nparts;
	int			fc_i;

	Assert(fc_nmerged > 0);
	Assert(*fc_outer_parts == NIL);
	Assert(*fc_inner_parts == NIL);

	fc_outer_indexes = (int *) palloc(sizeof(int) * fc_nmerged);
	fc_inner_indexes = (int *) palloc(sizeof(int) * fc_nmerged);
	for (fc_i = 0; fc_i < fc_nmerged; fc_i++)
		fc_outer_indexes[fc_i] = fc_inner_indexes[fc_i] = -1;

	/* 设置匹配分区对。 */
	Assert(fc_outer_nparts == fc_outer_rel->nparts);
	Assert(fc_inner_nparts == fc_inner_rel->nparts);
	fc_max_nparts = Max(fc_outer_nparts, fc_inner_nparts);
	for (fc_i = 0; fc_i < fc_max_nparts; fc_i++)
	{
		if (fc_i < fc_outer_nparts)
		{
			int			fc_merged_index = fc_outer_map->merged_indexes[fc_i];

			if (fc_merged_index >= 0)
			{
				Assert(fc_merged_index < fc_nmerged);
				fc_outer_indexes[fc_merged_index] = fc_i;
			}
		}
		if (fc_i < fc_inner_nparts)
		{
			int			fc_merged_index = fc_inner_map->merged_indexes[fc_i];

			if (fc_merged_index >= 0)
			{
				Assert(fc_merged_index < fc_nmerged);
				fc_inner_indexes[fc_merged_index] = fc_i;
			}
		}
	}

	/* 构建列表对。 */
	for (fc_i = 0; fc_i < fc_nmerged; fc_i++)
	{
		int			fc_outer_index = fc_outer_indexes[fc_i];
		int			fc_inner_index = fc_inner_indexes[fc_i];

		/*
		 * 如果两个分区都是虚拟的，这意味着分配给外侧/内侧分区的合并分区在
		 * merge_matching_partitions() 中重新合并外侧/内侧分区时被移除了；忽略
		 * 合并的分区。
		 */
		if (fc_outer_index == -1 && fc_inner_index == -1)
			continue;

		*fc_outer_parts = lappend(*fc_outer_parts, fc_outer_index >= 0 ?
							   fc_outer_rel->part_rels[fc_outer_index] : NULL);
		*fc_inner_parts = lappend(*fc_inner_parts, fc_inner_index >= 0 ?
							   fc_inner_rel->part_rels[fc_inner_index] : NULL);
	}

	pfree(fc_outer_indexes);
	pfree(fc_inner_indexes);
}

/*
 * build_merged_partition_bounds
 *		从合并分区界限创建 PartitionBoundInfo 结构
 */
static PartitionBoundInfo fc_build_merged_partition_bounds(char fc_strategy, List *fc_merged_datums,
							  List *fc_merged_kinds, List *fc_merged_indexes,
							  int fc_null_index, int fc_default_index)
{
	PartitionBoundInfo fc_merged_bounds;
	int			fc_ndatums = list_length(fc_merged_datums);
	int			fc_pos;
	ListCell   *fc_lc;

	fc_merged_bounds = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
	fc_merged_bounds->strategy = fc_strategy;
	fc_merged_bounds->ndatums = fc_ndatums;

	fc_merged_bounds->datums = (Datum **) palloc(sizeof(Datum *) * fc_ndatums);
	fc_pos = 0;
	foreach(fc_lc, fc_merged_datums)
		fc_merged_bounds->datums[fc_pos++] = (Datum *) lfirst(fc_lc);

	if (fc_strategy == PARTITION_STRATEGY_RANGE)
	{
		Assert(list_length(fc_merged_kinds) == fc_ndatums);
		fc_merged_bounds->kind = (PartitionRangeDatumKind **)
			palloc(sizeof(PartitionRangeDatumKind *) * fc_ndatums);
		fc_pos = 0;
		foreach(fc_lc, fc_merged_kinds)
			fc_merged_bounds->kind[fc_pos++] = (PartitionRangeDatumKind *) lfirst(fc_lc);

		/* 在区间分区的情况下有 ndatums+1 个索引。 */
		fc_merged_indexes = lappend_int(fc_merged_indexes, -1);
		fc_ndatums++;
	}
	else
	{
		Assert(fc_strategy == PARTITION_STRATEGY_LIST);
		Assert(fc_merged_kinds == NIL);
		fc_merged_bounds->kind = NULL;
	}

	/* 对于连接关系，interleaved_parts 总是 NULL。 */
	fc_merged_bounds->interleaved_parts = NULL;

	Assert(list_length(fc_merged_indexes) == fc_ndatums);
	fc_merged_bounds->nindexes = fc_ndatums;
	fc_merged_bounds->indexes = (int *) palloc(sizeof(int) * fc_ndatums);
	fc_pos = 0;
	foreach(fc_lc, fc_merged_indexes)
		fc_merged_bounds->indexes[fc_pos++] = lfirst_int(fc_lc);

	fc_merged_bounds->null_index = fc_null_index;
	fc_merged_bounds->default_index = fc_default_index;

	return fc_merged_bounds;
}

/*
 * get_range_partition
 *		获取范围分区关系的下一个非空分区，
 *		返回该分区的索引
 *
 * *lb 和 *ub 分别被设置为该分区的下限和上限，且 *lb_pos 如果有的话
 * 进展到下一个下限。
 */
static int fc_get_range_partition(RelOptInfo *fc_rel,
					PartitionBoundInfo fc_bi,
					int *fc_lb_pos,
					PartitionRangeBound *fc_lb,
					PartitionRangeBound *fc_ub)
{
	int			fc_part_index;

	Assert(fc_bi->strategy == PARTITION_STRATEGY_RANGE);

	do
	{
		fc_part_index = fc_get_range_partition_internal(fc_bi, fc_lb_pos, fc_lb, fc_ub);
		if (fc_part_index == -1)
			return -1;
	} while (fc_is_dummy_partition(fc_rel, fc_part_index));

	return fc_part_index;
}

static int fc_get_range_partition_internal(PartitionBoundInfo fc_bi,
							 int *fc_lb_pos,
							 PartitionRangeBound *fc_lb,
							 PartitionRangeBound *fc_ub)
{
	/* 如果我们已经耗尽所有下限，则返回索引为 -1。 */
	if (*fc_lb_pos >= fc_bi->ndatums)
		return -1;

	/* 下限后应至少有一个更高的界限。 */
	Assert(*fc_lb_pos + 1 < fc_bi->ndatums);

	/* 设置下界。 */
	fc_lb->index = fc_bi->indexes[*fc_lb_pos];
	fc_lb->datums = fc_bi->datums[*fc_lb_pos];
	fc_lb->kind = fc_bi->kind[*fc_lb_pos];
	fc_lb->lower = true;
	/* 设置上界。 */
	fc_ub->index = fc_bi->indexes[*fc_lb_pos + 1];
	fc_ub->datums = fc_bi->datums[*fc_lb_pos + 1];
	fc_ub->kind = fc_bi->kind[*fc_lb_pos + 1];
	fc_ub->lower = false;

	/* 分配给上限的索引应该是有效的。 */
	Assert(fc_ub->index >= 0);

	/*
	 * 将位置推进到下一个下限。如果在上限之外没有剩余的界限，
	 * 我们就达到了最后一个下限。
	 */
	if (*fc_lb_pos + 2 >= fc_bi->ndatums)
		*fc_lb_pos = fc_bi->ndatums;
	else
	{
		/*
		 * 如果分配给上限旁边的边界的索引无效，
		 * 那就是下一个下限；否则，上限也是下一个
		 * 范围分区的下限。
		 */
		if (fc_bi->indexes[*fc_lb_pos + 2] < 0)
			*fc_lb_pos = *fc_lb_pos + 2;
		else
			*fc_lb_pos = *fc_lb_pos + 1;
	}

	return fc_ub->index;
}

/*
 * compare_range_partitions
 *		比较两个范围分区的界限，如果两个分区重叠则返回真，
 *		否则返回假
 *
 * 如果外分区的下限低于、等于或高于内分区的下限，
 * 则 *lb_cmpval 分别被设置为 -1、0 或 1。同样，
 * 如果外分区的上限低于、等于或高于内分区的上限，
 * 则 *ub_cmpval 分别被设置为 -1、0 或 1。
 */
static bool fc_compare_range_partitions(int fc_partnatts, FmgrInfo *fc_partsupfuncs,
						 Oid *fc_partcollations,
						 PartitionRangeBound *fc_outer_lb,
						 PartitionRangeBound *fc_outer_ub,
						 PartitionRangeBound *fc_inner_lb,
						 PartitionRangeBound *fc_inner_ub,
						 int *fc_lb_cmpval, int *fc_ub_cmpval)
{
	/*
	 * 检查外分区的上界是否低于内分区的下界；如果是，
	 * 则分区不重叠。
	 */
	if (compare_range_bounds(fc_partnatts, fc_partsupfuncs, fc_partcollations,
							 fc_outer_ub, fc_inner_lb) < 0)
	{
		*fc_lb_cmpval = -1;
		*fc_ub_cmpval = -1;
		return false;
	}

	/*
	 * 检查外分区的下界是否高于内分区的上界；如果是，
	 * 则分区不重叠。
	 */
	if (compare_range_bounds(fc_partnatts, fc_partsupfuncs, fc_partcollations,
							 fc_outer_lb, fc_inner_ub) > 0)
	{
		*fc_lb_cmpval = 1;
		*fc_ub_cmpval = 1;
		return false;
	}

	/* 所有其他情况表示分区重叠。 */
	*fc_lb_cmpval = compare_range_bounds(fc_partnatts, fc_partsupfuncs, fc_partcollations,
									  fc_outer_lb, fc_inner_lb);
	*fc_ub_cmpval = compare_range_bounds(fc_partnatts, fc_partsupfuncs, fc_partcollations,
									  fc_outer_ub, fc_inner_ub);
	return true;
}

/*
 * get_merged_range_bounds
 *		给定要合并的范围分区的界限，确定从
 *		范围分区生成的合并分区的界限
 *
 * *merged_lb 和 *merged_ub 被设置为合并分区的下限和上限。
 */
static void fc_get_merged_range_bounds(int fc_partnatts, FmgrInfo *fc_partsupfuncs,
						Oid *fc_partcollations, JoinType fc_jointype,
						PartitionRangeBound *fc_outer_lb,
						PartitionRangeBound *fc_outer_ub,
						PartitionRangeBound *fc_inner_lb,
						PartitionRangeBound *fc_inner_ub,
						int fc_lb_cmpval, int fc_ub_cmpval,
						PartitionRangeBound *fc_merged_lb,
						PartitionRangeBound *fc_merged_ub)
{
	Assert(compare_range_bounds(fc_partnatts, fc_partsupfuncs, fc_partcollations,
								fc_outer_lb, fc_inner_lb) == fc_lb_cmpval);
	Assert(compare_range_bounds(fc_partnatts, fc_partsupfuncs, fc_partcollations,
								fc_outer_ub, fc_inner_ub) == fc_ub_cmpval);

	switch (fc_jointype)
	{
		case JOIN_INNER:
		case JOIN_SEMI:

			/*
			 * 一个 INNER/SEMI 连接将包含适合双方的行，
			 * 因此合并分区的下限将是两个下限中的较高者，
			 * 合并分区的上限将是两个上限中的较低者。
			 */
			*fc_merged_lb = (fc_lb_cmpval > 0) ? *fc_outer_lb : *fc_inner_lb;
			*fc_merged_ub = (fc_ub_cmpval < 0) ? *fc_outer_ub : *fc_inner_ub;
			break;

		case JOIN_LEFT:
		case JOIN_ANTI:

			/*
			 * LEFT/ANTI 连接将包含外侧的所有行，
			 * 因此合并分区的界限将与外部界限相同。
			 */
			*fc_merged_lb = *fc_outer_lb;
			*fc_merged_ub = *fc_outer_ub;
			break;

		case JOIN_FULL:

			/*
			 * FULL 连接将包含双方的所有行，
			 * 因此合并分区的下限将是两个下限中的较低者，
			 * 合并分区的上限将是两个上限中的较高者。
			 */
			*fc_merged_lb = (fc_lb_cmpval < 0) ? *fc_outer_lb : *fc_inner_lb;
			*fc_merged_ub = (fc_ub_cmpval > 0) ? *fc_outer_ub : *fc_inner_ub;
			break;

		default:
			elog(ERROR, "unrecognized join type: %d", (int) fc_jointype);
	}
}

/*
 * add_merged_range_bounds
 *		将合并分区的界限添加到范围界限的列表中
 */
static void fc_add_merged_range_bounds(int fc_partnatts, FmgrInfo *fc_partsupfuncs,
						Oid *fc_partcollations,
						PartitionRangeBound *fc_merged_lb,
						PartitionRangeBound *fc_merged_ub,
						int fc_merged_index,
						List **fc_merged_datums,
						List **fc_merged_kinds,
						List **fc_merged_indexes)
{
	int			fc_cmpval;

	if (!*fc_merged_datums)
	{
		/* 第一个合并分区 */
		Assert(!*fc_merged_kinds);
		Assert(!*fc_merged_indexes);
		fc_cmpval = 1;
	}
	else
	{
		PartitionRangeBound fc_prev_ub;

		Assert(*fc_merged_datums);
		Assert(*fc_merged_kinds);
		Assert(*fc_merged_indexes);

		/* 获取最后一个上限。 */
		fc_prev_ub.index = llast_int(*fc_merged_indexes);
		fc_prev_ub.datums = (Datum *) llast(*fc_merged_datums);
		fc_prev_ub.kind = (PartitionRangeDatumKind *) llast(*fc_merged_kinds);
		fc_prev_ub.lower = false;

		/*
		 * 我们将 lower1 = false 传递给 partition_rbound_cmp() 以防止其
		 * 将最后的上界视为小于合并分区的下界当两个范围界限的值相等时。
		 */
		fc_cmpval = fc_partition_rbound_cmp(fc_partnatts, fc_partsupfuncs, fc_partcollations,
									  fc_merged_lb->datums, fc_merged_lb->kind,
									  false, &fc_prev_ub);
		Assert(fc_cmpval >= 0);
	}

	/*
	 * 如果下界高于最后的上界，则添加下界及其索引为 -1，表示那是一个下界；否则，
	 * 将重用最后的上界作为合并分区的下界，因此跳过此操作。
	 */
	if (fc_cmpval > 0)
	{
		*fc_merged_datums = lappend(*fc_merged_datums, fc_merged_lb->datums);
		*fc_merged_kinds = lappend(*fc_merged_kinds, fc_merged_lb->kind);
		*fc_merged_indexes = lappend_int(*fc_merged_indexes, -1);
	}

	/* 添加合并分区的上界和索引。 */
	*fc_merged_datums = lappend(*fc_merged_datums, fc_merged_ub->datums);
	*fc_merged_kinds = lappend(*fc_merged_kinds, fc_merged_ub->kind);
	*fc_merged_indexes = lappend_int(*fc_merged_indexes, fc_merged_index);
}

/*
 * partitions_are_ordered
 *		确定 'boundinfo' 描述的分区是否有序，
 *		即在 PartitionDesc 序列中，较早出现的分区键
 *		严格小于后出现的分区键。
 *		此外，如果可能存在 NULL 值，它们必须出现在
 *		PartitionDesc 中定义的最后一个分区内。 'live_parts' 标记
 *		我们在检查排序时应包含哪些分区。
 *		不出现在 'live_parts' 中的分区会被忽略。
 *
 * 如果顺序错误，或没有足够的信息来确定顺序，
 * 则返回 false。
 */
bool partitions_are_ordered(PartitionBoundInfo fc_boundinfo, Bitmapset *fc_live_parts)
{
	Assert(fc_boundinfo != NULL);

	switch (fc_boundinfo->strategy)
	{
		case PARTITION_STRATEGY_RANGE:

			/*
			 * RANGE 类型的分区保证可以按照在 PartitionDesc 中
			 * 定义的顺序扫描分区，以提供顺序的、非重叠的元组范围。
			 * 然而，如果存在 DEFAULT 分区并且它包含在 live_parts 中，
			 * 则分区不是有序的。
			 */
			if (!partition_bound_has_default(fc_boundinfo) ||
				!bms_is_member(fc_boundinfo->default_index, fc_live_parts))
				return true;
			break;

		case PARTITION_STRATEGY_LIST:

			/*
			 * LIST 分区是有序的，只要 live_parts
			 * 不与分区表的交错分区重叠。
			 */
			if (!bms_overlap(fc_live_parts, fc_boundinfo->interleaved_parts))
				return true;

			break;
		default:
			/* HASH，或其他某种策略 */
			break;
	}

	return false;
}

/*
 * check_new_partition_bound
 *
 * 检查新分区的界限是否与父级的任何现有分区重叠。
 * 还根据策略执行必要的附加检查。
 */
void check_new_partition_bound(char *fc_relname, Relation fc_parent,
						  PartitionBoundSpec *fc_spec, ParseState *fc_pstate)
{
	PartitionKey fc_key = RelationGetPartitionKey(fc_parent);
	PartitionDesc fc_partdesc = RelationGetPartitionDesc(fc_parent, false);
	PartitionBoundInfo fc_boundinfo = fc_partdesc->boundinfo;
	int			fc_with = -1;
	bool		fc_overlap = false;
	int			fc_overlap_location = -1;

	if (fc_spec->is_default)
	{
		/*
		 * 默认分区界限永远不会与任何其他分区冲突；
		 * 如果我们要附加的是这个唯一可能的问题就是已经存在一个，
		 * 所以检查这一点，我们就完成了。
		 */
		if (fc_boundinfo == NULL || !partition_bound_has_default(fc_boundinfo))
			return;

		/* 默认分区已经存在，返回错误。 */
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("partition \"%s\" conflicts with existing default partition \"%s\"",
						fc_relname, get_rel_name(fc_partdesc->oids[fc_boundinfo->default_index])),
				 parser_errposition(fc_pstate, fc_spec->location)));
	}

	switch (fc_key->strategy)
	{
		case PARTITION_STRATEGY_HASH:
			{
				Assert(fc_spec->strategy == PARTITION_STRATEGY_HASH);
				Assert(fc_spec->remainder >= 0 && fc_spec->remainder < fc_spec->modulus);

				if (fc_partdesc->nparts > 0)
				{
					int			fc_greatest_modulus;
					int			fc_remainder;
					int			fc_offset;

					/*
					 * 检查规则，即每个模数必须是下一个较大模数的因子。
					 * （例如，如果你有一堆模数为 5 的分区，则可以添加
					 * 一个模数为 10 的新分区或一个模数为 15 的新分区，
					 * 但不能同时添加一个模数为 10 的分区和一个模数为 15 的分区，
					 * 因为 10 不是 15 的因子。）我们只需检查下一个
					 * 较小和下一个较大的现有模数，依赖于之前对该规则的实施
					 * 以确保其余部分符合要求。
					 */

					/*
					 * 获取包含在 boundinfo->datums 中且小于或等于
					 * (spec->modulus, spec->remainder) 对的最大
					 * (模数, 余数) 对。
					 */
					fc_offset = partition_hash_bsearch(fc_boundinfo,
													fc_spec->modulus,
													fc_spec->remainder);
					if (fc_offset < 0)
					{
						int			fc_next_modulus;

						/*
						 * 所有现有的模数都大于或等于，因此新的模数必须是最小模数的因子，最小模数在boundinfo中位于第一位。
						 */
						fc_next_modulus = DatumGetInt32(fc_boundinfo->datums[0][0]);
						if (fc_next_modulus % fc_spec->modulus != 0)
							ereport(ERROR,
									(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
									 errmsg("every hash partition modulus must be a factor of the next larger modulus"),
									 errdetail("The new modulus %d is not a factor of %d, the modulus of existing partition \"%s\".",
											   fc_spec->modulus, fc_next_modulus,
											   get_rel_name(fc_partdesc->oids[0]))));
					}
					else
					{
						int			fc_prev_modulus;

						/*
						 * 我们找到了一个最大的（模数，余数）对，小于或等于新的模数。那个模数必须是新的模数的一个因数或相等于新的模数。
						 */
						fc_prev_modulus = DatumGetInt32(fc_boundinfo->datums[fc_offset][0]);

						if (fc_spec->modulus % fc_prev_modulus != 0)
							ereport(ERROR,
									(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
									 errmsg("every hash partition modulus must be a factor of the next larger modulus"),
									 errdetail("The new modulus %d is not divisible by %d, the modulus of existing partition \"%s\".",
											   fc_spec->modulus,
											   fc_prev_modulus,
											   get_rel_name(fc_partdesc->oids[fc_offset]))));

						if (fc_offset + 1 < fc_boundinfo->ndatums)
						{
							int			fc_next_modulus;

							/*
							 * 查看下一个更高的（模数，余数）对。那个可能具有相同的模数，并且余数大于新的对，在这种情况下我们就可以了。如果它具有更大的模数，新的模数必须能够整除那个模数。
							 */
							fc_next_modulus = DatumGetInt32(fc_boundinfo->datums[fc_offset + 1][0]);

							if (fc_next_modulus % fc_spec->modulus != 0)
								ereport(ERROR,
										(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
										 errmsg("every hash partition modulus must be a factor of the next larger modulus"),
										 errdetail("The new modulus %d is not a factor of %d, the modulus of existing partition \"%s\".",
												   fc_spec->modulus, fc_next_modulus,
												   get_rel_name(fc_partdesc->oids[fc_offset + 1]))));
						}
					}

					fc_greatest_modulus = fc_boundinfo->nindexes;
					fc_remainder = fc_spec->remainder;

					/*
					 * 通常，可能与新的分区冲突的最低余数等于新分区指定的余数，但当新的分区的模数高于迄今为止使用的任何模数时，我们需要进行调整。
					 */
					if (fc_remainder >= fc_greatest_modulus)
						fc_remainder = fc_remainder % fc_greatest_modulus;

					/* 检查每一个可能冲突的余数。 */
					do
					{
						if (fc_boundinfo->indexes[fc_remainder] != -1)
						{
							fc_overlap = true;
							fc_overlap_location = fc_spec->location;
							fc_with = fc_boundinfo->indexes[fc_remainder];
							break;
						}
						fc_remainder += fc_spec->modulus;
					} while (fc_remainder < fc_greatest_modulus);
				}

				break;
			}

		case PARTITION_STRATEGY_LIST:
			{
				Assert(fc_spec->strategy == PARTITION_STRATEGY_LIST);

				if (fc_partdesc->nparts > 0)
				{
					ListCell   *fc_cell;

					Assert(fc_boundinfo &&
						   fc_boundinfo->strategy == PARTITION_STRATEGY_LIST &&
						   (fc_boundinfo->ndatums > 0 ||
							partition_bound_accepts_nulls(fc_boundinfo) ||
							partition_bound_has_default(fc_boundinfo)));

					foreach(fc_cell, fc_spec->listdatums)
					{
						Const	   *fc_val = lfirst_node(Const, fc_cell);

						fc_overlap_location = fc_val->location;
						if (!fc_val->constisnull)
						{
							int			fc_offset;
							bool		fc_equal;

							fc_offset = partition_list_bsearch(&fc_key->partsupfunc[0],
															fc_key->partcollation,
															fc_boundinfo,
															fc_val->constvalue,
															&fc_equal);
							if (fc_offset >= 0 && fc_equal)
							{
								fc_overlap = true;
								fc_with = fc_boundinfo->indexes[fc_offset];
								break;
							}
						}
						else if (partition_bound_accepts_nulls(fc_boundinfo))
						{
							fc_overlap = true;
							fc_with = fc_boundinfo->null_index;
							break;
						}
					}
				}

				break;
			}

		case PARTITION_STRATEGY_RANGE:
			{
				PartitionRangeBound *fc_lower,
						   *fc_upper;
				int			fc_cmpval;

				Assert(fc_spec->strategy == PARTITION_STRATEGY_RANGE);
				fc_lower = fc_make_one_partition_rbound(fc_key, -1, fc_spec->lowerdatums, true);
				fc_upper = fc_make_one_partition_rbound(fc_key, -1, fc_spec->upperdatums, false);

				/*
				 * 首先检查如果指定的下界和上界下，结果范围是否会为空。partition_rbound_cmp 在这里不能返回零，因为下界标志是不同的。
				 */
				fc_cmpval = fc_partition_rbound_cmp(fc_key->partnatts,
											  fc_key->partsupfunc,
											  fc_key->partcollation,
											  fc_lower->datums, fc_lower->kind,
											  true, fc_upper);
				Assert(fc_cmpval != 0);
				if (fc_cmpval > 0)
				{
					/* 指向下方数据列表中的问题键。 */
					PartitionRangeDatum *fc_datum = list_nth(fc_spec->lowerdatums,
														  fc_cmpval - 1);

					ereport(ERROR,
							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
							 errmsg("empty range bound specified for partition \"%s\"",
									fc_relname),
							 errdetail("Specified lower bound %s is greater than or equal to upper bound %s.",
									   get_range_partbound_string(fc_spec->lowerdatums),
									   get_range_partbound_string(fc_spec->upperdatums)),
							 parser_errposition(fc_pstate, fc_datum->location)));
				}

				if (fc_partdesc->nparts > 0)
				{
					int			fc_offset;

					Assert(fc_boundinfo &&
						   fc_boundinfo->strategy == PARTITION_STRATEGY_RANGE &&
						   (fc_boundinfo->ndatums > 0 ||
							partition_bound_has_default(fc_boundinfo)));

					/*
					 * 测试新的下界（视为新分区的一部分，包含在内）是否位于现有分区内或在空隙中。
					 *
					 * 如果它位于现有的分区中，则偏移量 + 1 处的界限将是该分区的上界，其索引将 >= 0。
					 *
					 * 如果它在空隙中，则偏移量 + 1 处的界限将是下一个分区的下界，其索引将为 -1。如果没有下一个分区，这也成立，因为索引数组在末尾初始化为额外的 -1。
					 */
					fc_offset = fc_partition_range_bsearch(fc_key->partnatts,
													 fc_key->partsupfunc,
													 fc_key->partcollation,
													 fc_boundinfo, fc_lower,
													 &fc_cmpval);

					if (fc_boundinfo->indexes[fc_offset + 1] < 0)
					{
						/*
						 * 检查新的分区是否适合空隙。为了使其适合，新的上界必须小于或等于下一个分区的下界（如果存在的话）。
						 */
						if (fc_offset + 1 < fc_boundinfo->ndatums)
						{
							Datum	   *fc_datums;
							PartitionRangeDatumKind *fc_kind;
							bool		fc_is_lower;

							fc_datums = fc_boundinfo->datums[fc_offset + 1];
							fc_kind = fc_boundinfo->kind[fc_offset + 1];
							fc_is_lower = (fc_boundinfo->indexes[fc_offset + 1] == -1);

							fc_cmpval = fc_partition_rbound_cmp(fc_key->partnatts,
														  fc_key->partsupfunc,
														  fc_key->partcollation,
														  fc_datums, fc_kind,
														  fc_is_lower, fc_upper);
							if (fc_cmpval < 0)
							{
								/*
								 * 指向上方数据列表中的问题键。
								 */
								PartitionRangeDatum *fc_datum =
								list_nth(fc_spec->upperdatums, Abs(fc_cmpval) - 1);

								/*
								 * 新分区与偏移量 + 1 和偏移量 + 2 之间的现有分区重叠。
								 */
								fc_overlap = true;
								fc_overlap_location = fc_datum->location;
								fc_with = fc_boundinfo->indexes[fc_offset + 2];
							}
						}
					}
					else
					{
						/*
						 * 新分区与偏移量和偏移量 + 1 之间的现有分区重叠。
						 */
						PartitionRangeDatum *fc_datum;

						/*
						 * 指向下方数据列表中的问题键；如果我们有相等，指向第一个。
						 */
						fc_datum = fc_cmpval == 0 ? linitial(fc_spec->lowerdatums) :
							list_nth(fc_spec->lowerdatums, Abs(fc_cmpval) - 1);
						fc_overlap = true;
						fc_overlap_location = fc_datum->location;
						fc_with = fc_boundinfo->indexes[fc_offset + 1];
					}
				}

				break;
			}

		default:
			elog(ERROR, "unexpected partition strategy: %d",
				 (int) fc_key->strategy);
	}

	if (fc_overlap)
	{
		Assert(fc_with >= 0);
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
				 errmsg("partition \"%s\" would overlap partition \"%s\"",
						fc_relname, get_rel_name(fc_partdesc->oids[fc_with])),
				 parser_errposition(fc_pstate, fc_overlap_location)));
	}
}

/*
 * check_default_partition_contents
 *
 * 此函数检查默认分区中是否存在属于新添加分区的行。如果找到，则抛出错误。
 */
void check_default_partition_contents(Relation fc_parent, Relation fc_default_rel,
								 PartitionBoundSpec *fc_new_spec)
{
	List	   *fc_new_part_constraints;
	List	   *fc_def_part_constraints;
	List	   *fc_all_parts;
	ListCell   *fc_lc;

	fc_new_part_constraints = (fc_new_spec->strategy == PARTITION_STRATEGY_LIST)
		? fc_get_qual_for_list(fc_parent, fc_new_spec)
		: fc_get_qual_for_range(fc_parent, fc_new_spec, false);
	fc_def_part_constraints =
		get_proposed_default_constraint(fc_new_part_constraints);

	/*
	 * 在约束表达式中，从父项的 attnos 映射变量到 default_rel 的。
	 */
	fc_def_part_constraints =
		map_partition_varattnos(fc_def_part_constraints, 1, fc_default_rel,
								fc_parent);

	/*
	 * 如果默认分区的现有约束表明它将不包含任何属于新分区的行，我们可以避免扫描默认分区。
	 */
	if (PartConstraintImpliedByRelConstraint(fc_default_rel, fc_def_part_constraints))
	{
		ereport(DEBUG1,
				(errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
								 RelationGetRelationName(fc_default_rel))));
		return;
	}

	/*
	 * 扫描默认分区及其子分区，并检查不满足修订后分区约束的行。
	 */
	if (fc_default_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
		fc_all_parts = find_all_inheritors(RelationGetRelid(fc_default_rel),
										AccessExclusiveLock, NULL);
	else
		fc_all_parts = list_make1_oid(RelationGetRelid(fc_default_rel));

	foreach(fc_lc, fc_all_parts)
	{
		Oid			fc_part_relid = lfirst_oid(fc_lc);
		Relation	fc_part_rel;
		Expr	   *fc_partition_constraint;
		EState	   *fc_estate;
		ExprState  *fc_partqualstate = NULL;
		Snapshot	fc_snapshot;
		ExprContext *fc_econtext;
		TableScanDesc fc_scan;
		MemoryContext fc_oldCxt;
		TupleTableSlot *fc_tupslot;

		/* 锁定已在上面获取。 */
		if (fc_part_relid != RelationGetRelid(fc_default_rel))
		{
			fc_part_rel = table_open(fc_part_relid, NoLock);

			/*
			 * 将约束表达式中的Vars映射到default_rel的子分区。
			 */
			fc_partition_constraint = make_ands_explicit(fc_def_part_constraints);
			fc_partition_constraint = (Expr *)
				map_partition_varattnos((List *) fc_partition_constraint, 1,
										fc_part_rel, fc_default_rel);

			/*
			 * 如果默认分区子表上的分区约束表明它将不包含任何属于新分区的行，我们可以避免扫描子表。
			 */
			if (PartConstraintImpliedByRelConstraint(fc_part_rel,
													 fc_def_part_constraints))
			{
				ereport(DEBUG1,
						(errmsg_internal("updated partition constraint for default partition \"%s\" is implied by existing constraints",
										 RelationGetRelationName(fc_part_rel))));

				table_close(fc_part_rel, NoLock);
				continue;
			}
		}
		else
		{
			fc_part_rel = fc_default_rel;
			fc_partition_constraint = make_ands_explicit(fc_def_part_constraints);
		}

		/*
		 * 只有RELKIND_RELATION关系（即叶子分区）需要被扫描。
		 */
		if (fc_part_rel->rd_rel->relkind != RELKIND_RELATION)
		{
			if (fc_part_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
				ereport(WARNING,
						(errcode(ERRCODE_CHECK_VIOLATION),
						 errmsg("skipped scanning foreign table \"%s\" which is a partition of default partition \"%s\"",
								RelationGetRelationName(fc_part_rel),
								RelationGetRelationName(fc_default_rel))));

			if (RelationGetRelid(fc_default_rel) != RelationGetRelid(fc_part_rel))
				table_close(fc_part_rel, NoLock);

			continue;
		}

		fc_estate = CreateExecutorState();

		/* 为分区检查资格构建表达式执行状态 */
		fc_partqualstate = ExecPrepareExpr(fc_partition_constraint, fc_estate);

		fc_econtext = GetPerTupleExprContext(fc_estate);
		fc_snapshot = RegisterSnapshot(GetLatestSnapshot());
		fc_tupslot = table_slot_create(fc_part_rel, &fc_estate->es_tupleTable);
		fc_scan = table_beginscan(fc_part_rel, fc_snapshot, 0, NULL);

		/*
		 * 切换到每个元组的内存上下文，并在生成的每个元组
		 * 上重置它，以便我们不会泄漏内存。
		 */
		fc_oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(fc_estate));

		while (table_scan_getnextslot(fc_scan, ForwardScanDirection, fc_tupslot))
		{
			fc_econtext->ecxt_scantuple = fc_tupslot;

			if (!ExecCheck(fc_partqualstate, fc_econtext))
				ereport(ERROR,
						(errcode(ERRCODE_CHECK_VIOLATION),
						 errmsg("updated partition constraint for default partition \"%s\" would be violated by some row",
								RelationGetRelationName(fc_default_rel)),
						 errtable(fc_default_rel)));

			ResetExprContext(fc_econtext);
			CHECK_FOR_INTERRUPTS();
		}

		MemoryContextSwitchTo(fc_oldCxt);
		table_endscan(fc_scan);
		UnregisterSnapshot(fc_snapshot);
		ExecDropSingleTupleTableSlot(fc_tupslot);
		FreeExecutorState(fc_estate);

		if (RelationGetRelid(fc_default_rel) != RelationGetRelid(fc_part_rel))
			table_close(fc_part_rel, NoLock);	/* 保持锁定直到提交 */
	}
}

/*
 * get_hash_partition_greatest_modulus
 *
 * 返回哈希分区边界的最大模数。
 * 这在核心代码中不再使用，但我们保留它以防外部模块使用。
 */
int get_hash_partition_greatest_modulus(PartitionBoundInfo fc_bound)
{
	Assert(fc_bound && fc_bound->strategy == PARTITION_STRATEGY_HASH);
	return fc_bound->nindexes;
}

/*
 * make_one_partition_rbound
 *
 * 给定一组PartitionRangeDatum元素的列表和一个标志，返回一个PartitionRangeBound，
 * 该标志指示边界是否为下界。之所以制作成函数，是因为有多个地方想要使用这一功能。
 */
static PartitionRangeBound *
fc_make_one_partition_rbound(PartitionKey fc_key, int fc_index, List *fc_datums, bool fc_lower)
{
	PartitionRangeBound *fc_bound;
	ListCell   *fc_lc;
	int			fc_i;

	Assert(fc_datums != NIL);

	fc_bound = (PartitionRangeBound *) palloc0(sizeof(PartitionRangeBound));
	fc_bound->index = fc_index;
	fc_bound->datums = (Datum *) palloc0(fc_key->partnatts * sizeof(Datum));
	fc_bound->kind = (PartitionRangeDatumKind *) palloc0(fc_key->partnatts *
													  sizeof(PartitionRangeDatumKind));
	fc_bound->lower = fc_lower;

	fc_i = 0;
	foreach(fc_lc, fc_datums)
	{
		PartitionRangeDatum *fc_datum = lfirst_node(PartitionRangeDatum, fc_lc);

		/* 这个范围数据中包含什么？ */
		fc_bound->kind[fc_i] = fc_datum->kind;

		if (fc_datum->kind == PARTITION_RANGE_DATUM_VALUE)
		{
			Const	   *fc_val = castNode(Const, fc_datum->value);

			if (fc_val->constisnull)
				elog(ERROR, "invalid range bound datum");
			fc_bound->datums[fc_i] = fc_val->constvalue;
		}

		fc_i++;
	}

	return fc_bound;
}

/*
 * partition_rbound_cmp
 *
 * 对于两个范围边界，决定第一个（由datums1、kind1和lower1指定）是否小于、等于或大于在*b2中指定的边界。
 *
 * 如果它们相等，则返回0，否则返回一个非零整数，其符号表示顺序，绝对值给出第一个不匹配列的基于1的分区键编号。
 *
 * partnatts、partsupfunc和partcollation分别提供要比较的边界中的属性数量、要使用的比较函数和属性的排序。
 *
 * 请注意，如果两个范围边界的值比较相等，则考虑它们是上界还是下界，上界被视为小于下界。这对于RelationBuildPartitionDesc()构建PartitionBoundInfoData结构的方式非常重要，
 * 该结构只存储两个相邻分区之间公共边界的上界。
 */
static int32 fc_partition_rbound_cmp(int fc_partnatts, FmgrInfo *fc_partsupfunc,
					 Oid *fc_partcollation,
					 Datum *fc_datums1, PartitionRangeDatumKind *fc_kind1,
					 bool fc_lower1, PartitionRangeBound *fc_b2)
{
	int32		fc_colnum = 0;
	int32		fc_cmpval = 0;		/* 安抚编译器 */
	int			fc_i;
	Datum	   *fc_datums2 = fc_b2->datums;
	PartitionRangeDatumKind *fc_kind2 = fc_b2->kind;
	bool		fc_lower2 = fc_b2->lower;

	for (fc_i = 0; fc_i < fc_partnatts; fc_i++)
	{
		/* 跟踪列号以防我们需要它作为结果 */
		fc_colnum++;

		/*
		 * 首先，处理列无限制的情况，这不应调用比较程序，并且不应考虑任何后续列。
		 * 请注意，PartitionRangeDatumKind枚举元素的比较方式与它们表示的值相同。
		 */
		if (fc_kind1[fc_i] < fc_kind2[fc_i])
			return -fc_colnum;
		else if (fc_kind1[fc_i] > fc_kind2[fc_i])
			return fc_colnum;
		else if (fc_kind1[fc_i] != PARTITION_RANGE_DATUM_VALUE)
		{
			/*
			 * 列的边界要么都是MINVALUE，要么都是MAXVALUE。后面的列
			 * 不应该被考虑，但我们仍然需要比较它们是上界还是下界。
			 */
			break;
		}

		fc_cmpval = DatumGetInt32(FunctionCall2Coll(&fc_partsupfunc[fc_i],
												 fc_partcollation[fc_i],
												 fc_datums1[fc_i],
												 fc_datums2[fc_i]));
		if (fc_cmpval != 0)
			break;
	}

	/*
	 * 如果比较结果不是相等，那我们就完成了。如果它们相等，
	 * 我们仍然需要考虑边界是包容的还是排斥的。排斥的被认为是
	 * 两个中的较小者。
	 */
	if (fc_cmpval == 0 && fc_lower1 != fc_lower2)
		fc_cmpval = fc_lower1 ? 1 : -1;

	return fc_cmpval == 0 ? 0 : (fc_cmpval < 0 ? -fc_colnum : fc_colnum);
}

/*
 * partition_rbound_datum_cmp
 *
 * 返回范围边界（在 rb_datums 和 rb_kind 中指定）
 * 是否小于、等于或大于元组的分区键（tuple_datums）
 *
 * n_tuple_datums、partsupfunc 和 partcollation 分别给出
 * 要比较的边界中的属性数量、要使用的比较功能和属性的排序。
 */
int32 partition_rbound_datum_cmp(FmgrInfo *fc_partsupfunc, Oid *fc_partcollation,
						   Datum *fc_rb_datums, PartitionRangeDatumKind *fc_rb_kind,
						   Datum *fc_tuple_datums, int fc_n_tuple_datums)
{
	int			fc_i;
	int32		fc_cmpval = -1;

	for (fc_i = 0; fc_i < fc_n_tuple_datums; fc_i++)
	{
		if (fc_rb_kind[fc_i] == PARTITION_RANGE_DATUM_MINVALUE)
			return -1;
		else if (fc_rb_kind[fc_i] == PARTITION_RANGE_DATUM_MAXVALUE)
			return 1;

		fc_cmpval = DatumGetInt32(FunctionCall2Coll(&fc_partsupfunc[fc_i],
												 fc_partcollation[fc_i],
												 fc_rb_datums[fc_i],
												 fc_tuple_datums[fc_i]));
		if (fc_cmpval != 0)
			break;
	}

	return fc_cmpval;
}

/*
 * partition_hbound_cmp
 *
 * 首先比较模数，如果模数相等则比较余数。
 */
static int32 fc_partition_hbound_cmp(int fc_modulus1, int fc_remainder1, int fc_modulus2, int fc_remainder2)
{
	if (fc_modulus1 < fc_modulus2)
		return -1;
	if (fc_modulus1 > fc_modulus2)
		return 1;
	if (fc_modulus1 == fc_modulus2 && fc_remainder1 != fc_remainder2)
		return (fc_remainder1 > fc_remainder2) ? 1 : -1;
	return 0;
}

/*
 * partition_list_bsearch
 *		返回小于等于给定值的最大边界数据的索引，如果所有边界数据
 * 		都大于，则返回 -1。
 *
 * *is_equal 在返回的索引处边界数据等于输入值时设置为真。
 */
int partition_list_bsearch(FmgrInfo *fc_partsupfunc, Oid *fc_partcollation,
					   PartitionBoundInfo fc_boundinfo,
					   Datum fc_value, bool *fc_is_equal)
{
	int			fc_lo,
				fc_hi,
				fc_mid;

	fc_lo = -1;
	fc_hi = fc_boundinfo->ndatums - 1;
	while (fc_lo < fc_hi)
	{
		int32		fc_cmpval;

		fc_mid = (fc_lo + fc_hi + 1) / 2;
		fc_cmpval = DatumGetInt32(FunctionCall2Coll(&fc_partsupfunc[0],
												 fc_partcollation[0],
												 fc_boundinfo->datums[fc_mid][0],
												 fc_value));
		if (fc_cmpval <= 0)
		{
			fc_lo = fc_mid;
			*fc_is_equal = (fc_cmpval == 0);
			if (*fc_is_equal)
				break;
		}
		else
			fc_hi = fc_mid - 1;
	}

	return fc_lo;
}

/*
 * partition_range_bsearch
 *		返回小于或等于给定范围边界的最大范围边界的索引，
 *		如果所有范围边界都大于，则返回 -1。
 *
 * 从此函数返回时，如果返回的索引处的边界与输入范围边界完全匹配，
 * *cmpval 设置为 0，否则设置为一个非零整数，其符号表示顺序，
 * 其绝对值给出第一个不匹配列的 1 基数分区键号。
 */
static int fc_partition_range_bsearch(int fc_partnatts, FmgrInfo *fc_partsupfunc,
						Oid *fc_partcollation,
						PartitionBoundInfo fc_boundinfo,
						PartitionRangeBound *fc_probe, int32 *fc_cmpval)
{
	int			fc_lo,
				fc_hi,
				fc_mid;

	fc_lo = -1;
	fc_hi = fc_boundinfo->ndatums - 1;
	while (fc_lo < fc_hi)
	{
		fc_mid = (fc_lo + fc_hi + 1) / 2;
		*fc_cmpval = fc_partition_rbound_cmp(fc_partnatts, fc_partsupfunc,
									   fc_partcollation,
									   fc_boundinfo->datums[fc_mid],
									   fc_boundinfo->kind[fc_mid],
									   (fc_boundinfo->indexes[fc_mid] == -1),
									   fc_probe);
		if (*fc_cmpval <= 0)
		{
			fc_lo = fc_mid;
			if (*fc_cmpval == 0)
				break;
		}
		else
			fc_hi = fc_mid - 1;
	}

	return fc_lo;
}

/*
 * partition_range_datum_bsearch
 *		返回小于或等于给定元组的最大范围边界的索引，
 *		如果所有范围边界都大于，则返回 -1。
 *
 * *is_equal 在返回的索引处的范围边界等于输入元组时设置为真。
 */
int partition_range_datum_bsearch(FmgrInfo *fc_partsupfunc, Oid *fc_partcollation,
							  PartitionBoundInfo fc_boundinfo,
							  int fc_nvalues, Datum *fc_values, bool *fc_is_equal)
{
	int			fc_lo,
				fc_hi,
				fc_mid;

	fc_lo = -1;
	fc_hi = fc_boundinfo->ndatums - 1;
	while (fc_lo < fc_hi)
	{
		int32		fc_cmpval;

		fc_mid = (fc_lo + fc_hi + 1) / 2;
		fc_cmpval = partition_rbound_datum_cmp(fc_partsupfunc,
											fc_partcollation,
											fc_boundinfo->datums[fc_mid],
											fc_boundinfo->kind[fc_mid],
											fc_values,
											fc_nvalues);
		if (fc_cmpval <= 0)
		{
			fc_lo = fc_mid;
			*fc_is_equal = (fc_cmpval == 0);

			if (*fc_is_equal)
				break;
		}
		else
			fc_hi = fc_mid - 1;
	}

	return fc_lo;
}

/*
 * partition_hash_bsearch
 *		返回小于或等于给定（模数，余数）对的最大（模数，余数）
 *		对的索引，如果所有的都大于，则返回 -1。
 */
int partition_hash_bsearch(PartitionBoundInfo fc_boundinfo,
					   int fc_modulus, int fc_remainder)
{
	int			fc_lo,
				fc_hi,
				fc_mid;

	fc_lo = -1;
	fc_hi = fc_boundinfo->ndatums - 1;
	while (fc_lo < fc_hi)
	{
		int32		fc_cmpval,
					fc_bound_modulus,
					fc_bound_remainder;

		fc_mid = (fc_lo + fc_hi + 1) / 2;
		fc_bound_modulus = DatumGetInt32(fc_boundinfo->datums[fc_mid][0]);
		fc_bound_remainder = DatumGetInt32(fc_boundinfo->datums[fc_mid][1]);
		fc_cmpval = fc_partition_hbound_cmp(fc_bound_modulus, fc_bound_remainder,
									  fc_modulus, fc_remainder);
		if (fc_cmpval <= 0)
		{
			fc_lo = fc_mid;

			if (fc_cmpval == 0)
				break;
		}
		else
			fc_hi = fc_mid - 1;
	}

	return fc_lo;
}

/*
 * qsort_partition_hbound_cmp
 *
 * 哈希边界按模数排序，然后按余数排序。
 */
static int32 fc_qsort_partition_hbound_cmp(const void *fc_a, const void *fc_b)
{
	const PartitionHashBound *fc_h1 = (const PartitionHashBound *) fc_a;
	const PartitionHashBound *fc_h2 = (const PartitionHashBound *) fc_b;

	return fc_partition_hbound_cmp(fc_h1->modulus, fc_h1->remainder,
								fc_h2->modulus, fc_h2->remainder);
}

/*
 * qsort_partition_list_value_cmp
 *
 * 比较两个列表分区边界数据。
 */
static int32 fc_qsort_partition_list_value_cmp(const void *fc_a, const void *fc_b, void *fc_arg)
{
	Datum		fc_val1 = ((const PartitionListValue *) fc_a)->value,
				fc_val2 = ((const PartitionListValue *) fc_b)->value;
	PartitionKey fc_key = (PartitionKey) fc_arg;

	return DatumGetInt32(FunctionCall2Coll(&fc_key->partsupfunc[0],
										   fc_key->partcollation[0],
										   fc_val1, fc_val2));
}

/*
 * qsort_partition_rbound_cmp
 *
 * 用于对所有范围分区中的范围边界进行排序时。
 */
static int32 fc_qsort_partition_rbound_cmp(const void *fc_a, const void *fc_b, void *fc_arg)
{
	PartitionRangeBound *fc_b1 = (*(PartitionRangeBound *const *) fc_a);
	PartitionRangeBound *fc_b2 = (*(PartitionRangeBound *const *) fc_b);
	PartitionKey fc_key = (PartitionKey) fc_arg;

	return compare_range_bounds(fc_key->partnatts, fc_key->partsupfunc,
								fc_key->partcollation,
								fc_b1, fc_b2);
}

/*
 * get_partition_operator
 *
 * 返回给定分区键列的给定策略的操作符 oid。假定分区键与所选
 * 分区操作类的类型相同，或者至少是二进制兼容的。在后一种情况下，
 * 如果 opclass 不是多态类型（表示需要在上面有一个 RelabelType 节点），
 * 则 *need_relabel 被设置为真，否则为假。
 */
static Oid fc_get_partition_operator(PartitionKey fc_key, int fc_col, StrategyNumber fc_strategy,
					   bool *fc_need_relabel)
{
	Oid			fc_operoid;

	/*
	 * 获取分区操作族中使用 opclass 声明的输入类型作为左类型和右类型的操作符。
	 */
	fc_operoid = get_opfamily_member(fc_key->partopfamily[fc_col],
								  fc_key->partopcintype[fc_col],
								  fc_key->partopcintype[fc_col],
								  fc_strategy);
	if (!OidIsValid(fc_operoid))
		elog(ERROR, "missing operator %d(%u,%u) in partition opfamily %u",
			 fc_strategy, fc_key->partopcintype[fc_col], fc_key->partopcintype[fc_col],
			 fc_key->partopfamily[fc_col]);

	/*
	 * 如果分区键列的类型与操作符类不同且不是多态的，则告知调用者将非 Const 表达式包装在 RelabelType 中。这与 parse_coerce.c 的做法一致。
	 */
	*fc_need_relabel = (fc_key->parttypid[fc_col] != fc_key->partopcintype[fc_col] &&
					 fc_key->partopcintype[fc_col] != RECORDOID &&
					 !IsPolymorphicType(fc_key->partopcintype[fc_col]));

	return fc_operoid;
}

/*
 * make_partition_op_expr
 *		返回给定分区键列的 Expr，arg1 和 arg2 分别作为其左操作符和右操作符。
 */
static Expr * fc_make_partition_op_expr(PartitionKey fc_key, int fc_keynum,
					   uint16 fc_strategy, Expr *fc_arg1, Expr *fc_arg2)
{
	Oid			fc_operoid;
	bool		fc_need_relabel = false;
	Expr	   *fc_result = NULL;

	/* 获取该分区列的正确 btree 操作符 */
	fc_operoid = fc_get_partition_operator(fc_key, fc_keynum, fc_strategy, &fc_need_relabel);

	/*
	 * 选择的操作符可能需要将非 Const 操作数强制转换，因此应用相同的操作；参见 get_partition_operator() 中的注释。
	 */
	if (!IsA(fc_arg1, Const) &&
		(fc_need_relabel ||
		 fc_key->partcollation[fc_keynum] != fc_key->parttypcoll[fc_keynum]))
		fc_arg1 = (Expr *) makeRelabelType(fc_arg1,
										fc_key->partopcintype[fc_keynum],
										-1,
										fc_key->partcollation[fc_keynum],
										COERCE_EXPLICIT_CAST);

	/* 生成实际表达式 */
	switch (fc_key->strategy)
	{
		case PARTITION_STRATEGY_LIST:
			{
				List	   *fc_elems = (List *) fc_arg2;
				int			fc_nelems = list_length(fc_elems);

				Assert(fc_nelems >= 1);
				Assert(fc_keynum == 0);

				if (fc_nelems > 1 &&
					!type_is_array(fc_key->parttypid[fc_keynum]))
				{
					ArrayExpr  *fc_arrexpr;
					ScalarArrayOpExpr *fc_saopexpr;

					/* 为右侧输入构造 ArrayExpr */
					fc_arrexpr = makeNode(ArrayExpr);
					fc_arrexpr->array_typeid =
						get_array_type(fc_key->parttypid[fc_keynum]);
					fc_arrexpr->array_collid = fc_key->parttypcoll[fc_keynum];
					fc_arrexpr->element_typeid = fc_key->parttypid[fc_keynum];
					fc_arrexpr->elements = fc_elems;
					fc_arrexpr->multidims = false;
					fc_arrexpr->location = -1;

					/* 构建 leftop = ANY (rightop) */
					fc_saopexpr = makeNode(ScalarArrayOpExpr);
					fc_saopexpr->opno = fc_operoid;
					fc_saopexpr->opfuncid = get_opcode(fc_operoid);
					fc_saopexpr->hashfuncid = InvalidOid;
					fc_saopexpr->negfuncid = InvalidOid;
					fc_saopexpr->useOr = true;
					fc_saopexpr->inputcollid = fc_key->partcollation[fc_keynum];
					fc_saopexpr->args = list_make2(fc_arg1, fc_arrexpr);
					fc_saopexpr->location = -1;

					fc_result = (Expr *) fc_saopexpr;
				}
				else
				{
					List	   *fc_elemops = NIL;
					ListCell   *fc_lc;

					foreach(fc_lc, fc_elems)
					{
						Expr	   *fc_elem = lfirst(fc_lc),
								   *fc_elemop;

						fc_elemop = make_opclause(fc_operoid,
											   BOOLOID,
											   false,
											   fc_arg1, fc_elem,
											   InvalidOid,
											   fc_key->partcollation[fc_keynum]);
						fc_elemops = lappend(fc_elemops, fc_elemop);
					}

					fc_result = fc_nelems > 1 ? makeBoolExpr(OR_EXPR, fc_elemops, -1) : linitial(fc_elemops);
				}
				break;
			}

		case PARTITION_STRATEGY_RANGE:
			fc_result = make_opclause(fc_operoid,
								   BOOLOID,
								   false,
								   fc_arg1, fc_arg2,
								   InvalidOid,
								   fc_key->partcollation[fc_keynum]);
			break;

		default:
			elog(ERROR, "invalid partitioning strategy");
			break;
	}

	return fc_result;
}

/*
 * get_qual_for_hash
 *
 * 返回一个 CHECK 约束表达式，用作哈希分区的约束，给定父关系和分区边界结构。
 *
 * 哈希分区的分区约束总是对内置函数 satisfies_hash_partition() 的调用。
 */
static List * fc_get_qual_for_hash(Relation fc_parent, PartitionBoundSpec *fc_spec)
{
	PartitionKey fc_key = RelationGetPartitionKey(fc_parent);
	FuncExpr   *fc_fexpr;
	Node	   *fc_relidConst;
	Node	   *fc_modulusConst;
	Node	   *fc_remainderConst;
	List	   *fc_args;
	ListCell   *fc_partexprs_item;
	int			fc_i;

	/* 固定参数。 */
	fc_relidConst = (Node *) makeConst(OIDOID,
									-1,
									InvalidOid,
									sizeof(Oid),
									ObjectIdGetDatum(RelationGetRelid(fc_parent)),
									false,
									true);

	fc_modulusConst = (Node *) makeConst(INT4OID,
									  -1,
									  InvalidOid,
									  sizeof(int32),
									  Int32GetDatum(fc_spec->modulus),
									  false,
									  true);

	fc_remainderConst = (Node *) makeConst(INT4OID,
										-1,
										InvalidOid,
										sizeof(int32),
										Int32GetDatum(fc_spec->remainder),
										false,
										true);

	fc_args = list_make3(fc_relidConst, fc_modulusConst, fc_remainderConst);
	fc_partexprs_item = list_head(fc_key->partexprs);

	/* 为每个键列添加一个参数。 */
	for (fc_i = 0; fc_i < fc_key->partnatts; fc_i++)
	{
		Node	   *fc_keyCol;

		/* 左操作数 */
		if (fc_key->partattrs[fc_i] != 0)
		{
			fc_keyCol = (Node *) makeVar(1,
									  fc_key->partattrs[fc_i],
									  fc_key->parttypid[fc_i],
									  fc_key->parttypmod[fc_i],
									  fc_key->parttypcoll[fc_i],
									  0);
		}
		else
		{
			fc_keyCol = (Node *) copyObject(lfirst(fc_partexprs_item));
			fc_partexprs_item = lnext(fc_key->partexprs, fc_partexprs_item);
		}

		fc_args = lappend(fc_args, fc_keyCol);
	}

	fc_fexpr = makeFuncExpr(F_SATISFIES_HASH_PARTITION,
						 BOOLOID,
						 fc_args,
						 InvalidOid,
						 InvalidOid,
						 COERCE_EXPLICIT_CALL);

	return list_make1(fc_fexpr);
}

/*
 * get_qual_for_list
 *
 * 返回一个隐式 AND 表达式列表，用作列表分区的约束，给定父关系和分区边界结构。
 *
 * 当它是唯一分区时，该函数对默认分区返回 NIL，因为在这种情况下没有约束。
 */
static List * fc_get_qual_for_list(Relation fc_parent, PartitionBoundSpec *fc_spec)
{
	PartitionKey fc_key = RelationGetPartitionKey(fc_parent);
	List	   *fc_result;
	Expr	   *fc_keyCol;
	Expr	   *fc_opexpr;
	NullTest   *fc_nulltest;
	ListCell   *fc_cell;
	List	   *fc_elems = NIL;
	bool		fc_list_has_null = false;

	/*
	 * 仅支持单列列表分区，因此我们只关心索引为 0 的分区键。
	 */
	Assert(fc_key->partnatts == 1);

	/* 构造表示分区列的 Var 或表达式 */
	if (fc_key->partattrs[0] != 0)
		fc_keyCol = (Expr *) makeVar(1,
								  fc_key->partattrs[0],
								  fc_key->parttypid[0],
								  fc_key->parttypmod[0],
								  fc_key->parttypcoll[0],
								  0);
	else
		fc_keyCol = (Expr *) copyObject(linitial(fc_key->partexprs));

	/*
	 * 对于默认列表分区，收集所有分区的数值。默认分区约束应检查分区键是否等于那些值中的任何一个。
	 */
	if (fc_spec->is_default)
	{
		int			fc_i;
		int			fc_ndatums = 0;
		PartitionDesc fc_pdesc = RelationGetPartitionDesc(fc_parent, false);
		PartitionBoundInfo fc_boundinfo = fc_pdesc->boundinfo;

		if (fc_boundinfo)
		{
			fc_ndatums = fc_boundinfo->ndatums;

			if (partition_bound_accepts_nulls(fc_boundinfo))
				fc_list_has_null = true;
		}

		/*
		 * 如果默认分区是唯一的分区，则不需要对其施加任何分区约束。
		 */
		if (fc_ndatums == 0 && !fc_list_has_null)
			return NIL;

		for (fc_i = 0; fc_i < fc_ndatums; fc_i++)
		{
			Const	   *fc_val;

			/*
			 * 从已知非空的数值构造 Const。我们必须小心复制该值，因为我们的结果必须能够超越我们正在复制的 relcache 条目。
			 */
			fc_val = makeConst(fc_key->parttypid[0],
							fc_key->parttypmod[0],
							fc_key->parttypcoll[0],
							fc_key->parttyplen[0],
							datumCopy(*fc_boundinfo->datums[fc_i],
									  fc_key->parttypbyval[0],
									  fc_key->parttyplen[0]),
							false,	/* isnull */
							fc_key->parttypbyval[0]);

			fc_elems = lappend(fc_elems, fc_val);
		}
	}
	else
	{
		/*
		 * 为允许的值创建 Const 列表，排除任何空值。
		 */
		foreach(fc_cell, fc_spec->listdatums)
		{
			Const	   *fc_val = lfirst_node(Const, fc_cell);

			if (fc_val->constisnull)
				fc_list_has_null = true;
			else
				fc_elems = lappend(fc_elems, copyObject(fc_val));
		}
	}

	if (fc_elems)
	{
		/*
		 * 从非空分区值生成操作符表达式。
		 */
		fc_opexpr = fc_make_partition_op_expr(fc_key, 0, BTEqualStrategyNumber,
										fc_keyCol, (Expr *) fc_elems);
	}
	else
	{
		/*
		 * 如果没有分区值，则我们不需要操作符表达式。
		 */
		fc_opexpr = NULL;
	}

	if (!fc_list_has_null)
	{
		/*
		 * 创建一个“col IS NOT NULL”的测试，该测试将与主表达式进行 AND 操作。这看似多余，但分区路由机制需要它。
		 */
		fc_nulltest = makeNode(NullTest);
		fc_nulltest->arg = fc_keyCol;
		fc_nulltest->nulltesttype = IS_NOT_NULL;
		fc_nulltest->argisrow = false;
		fc_nulltest->location = -1;

		fc_result = fc_opexpr ? list_make2(fc_nulltest, fc_opexpr) : list_make1(fc_nulltest);
	}
	else
	{
		/*
		 * 创建一个“col IS NULL”的测试，该测试将与主表达式进行 OR 操作。
		 */
		fc_nulltest = makeNode(NullTest);
		fc_nulltest->arg = fc_keyCol;
		fc_nulltest->nulltesttype = IS_NULL;
		fc_nulltest->argisrow = false;
		fc_nulltest->location = -1;

		if (fc_opexpr)
		{
			Expr	   *fc_or;

			fc_or = makeBoolExpr(OR_EXPR, list_make2(fc_nulltest, fc_opexpr), -1);
			fc_result = list_make1(fc_or);
		}
		else
			fc_result = list_make1(fc_nulltest);
	}

	/*
	 * 请注意，一般来说，对约束表达式应用NOT并不一定会反转它接受的行集，因为NOT (NULL)是NULL。然而，我们在这里构造的分区约束从不评估为NULL，因此应用NOT符合预期。
	 */
	if (fc_spec->is_default)
	{
		fc_result = list_make1(make_ands_explicit(fc_result));
		fc_result = list_make1(makeBoolExpr(NOT_EXPR, fc_result, -1));
	}

	return fc_result;
}

/*
 * get_qual_for_range
 *
 * 返回一个隐式的 AND 表达式列表，用作范围分区的约束，给定父关系和分区边界结构。
 *
 * 对于多列范围分区键，比如(a, b, c)，下边界元组为(al, bl, cl)，上边界元组为(au, bu, cu)，我们生成如下形式的表达式树：
 *
 *	(a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
 *		AND
 *	(a > al OR (a = al AND b > bl) OR (a = al AND b = bl AND c >= cl))
 *		AND
 *	(a < au OR (a = au AND b < bu) OR (a = au AND b = bu AND c < cu))
 *
 * 通常情况下，下边界和上边界元组的前缀包含相同的值，例如(al = au)，在这种情况下，我们将生成如下形式的表达式树：
 *
 *	(a IS NOT NULL) and (b IS NOT NULL) and (c IS NOT NULL)
 *		AND
 *	(a = al)
 *		AND
 *	(b > bl OR (b = bl AND c >= cl))
 *		AND
 *	(b < bu OR (b = bu AND c < cu))
 *
 * 如果边界数据是MINVALUE或MAXVALUE，这些表达式会使用任何值大于MINVALUE且小于MAXVALUE的事实进行简化。因此，例如，如果cu = MAXVALUE，c < cu自动为真，我们不需要为此发出任何表达式，最后一行变为
 *
 *	(b < bu) OR (b = bu)，简化为(b <= bu)
 *
 * 在只有一个分区列的最常见情况下，比如a，将会生成以下表达式树：a IS NOT NULL AND a >= al AND a < au
 *
 * 对于默认分区，它返回所有其他分区约束的否定。
 *
 * 外部调用者应该将for_default传递为false；我们仅在递归时将其设置为true。
 */
static List * fc_get_qual_for_range(Relation fc_parent, PartitionBoundSpec *fc_spec,
				   bool fc_for_default)
{
	List	   *fc_result = NIL;
	ListCell   *fc_cell1,
			   *fc_cell2,
			   *fc_partexprs_item,
			   *fc_partexprs_item_saved;
	int			fc_i,
				fc_j;
	PartitionRangeDatum *fc_ldatum,
			   *fc_udatum;
	PartitionKey fc_key = RelationGetPartitionKey(fc_parent);
	Expr	   *fc_keyCol;
	Const	   *fc_lower_val,
			   *fc_upper_val;
	List	   *fc_lower_or_arms,
			   *fc_upper_or_arms;
	int			fc_num_or_arms,
				fc_current_or_arm;
	ListCell   *fc_lower_or_start_datum,
			   *fc_upper_or_start_datum;
	bool		fc_need_next_lower_arm,
				fc_need_next_upper_arm;

	if (fc_spec->is_default)
	{
		List	   *fc_or_expr_args = NIL;
		PartitionDesc fc_pdesc = RelationGetPartitionDesc(fc_parent, false);
		Oid		   *fc_inhoids = fc_pdesc->oids;
		int			fc_nparts = fc_pdesc->nparts,
					fc_i;

		for (fc_i = 0; fc_i < fc_nparts; fc_i++)
		{
			Oid			fc_inhrelid = fc_inhoids[fc_i];
			HeapTuple	fc_tuple;
			Datum		fc_datum;
			bool		fc_isnull;
			PartitionBoundSpec *fc_bspec;

			fc_tuple = SearchSysCache1(RELOID, fc_inhrelid);
			if (!HeapTupleIsValid(fc_tuple))
				elog(ERROR, "cache lookup failed for relation %u", fc_inhrelid);

			fc_datum = SysCacheGetAttr(RELOID, fc_tuple,
									Anum_pg_class_relpartbound,
									&fc_isnull);
			if (fc_isnull)
				elog(ERROR, "null relpartbound for relation %u", fc_inhrelid);

			fc_bspec = (PartitionBoundSpec *)
				stringToNode(TextDatumGetCString(fc_datum));
			if (!IsA(fc_bspec, PartitionBoundSpec))
				elog(ERROR, "expected PartitionBoundSpec");

			if (!fc_bspec->is_default)
			{
				List	   *fc_part_qual;

				fc_part_qual = fc_get_qual_for_range(fc_parent, fc_bspec, true);

				/*
				 * 添加分区的约束并加入or_expr_args
				 */
				fc_or_expr_args = lappend(fc_or_expr_args, list_length(fc_part_qual) > 1
									   ? makeBoolExpr(AND_EXPR, fc_part_qual, -1)
									   : linitial(fc_part_qual));
			}
			ReleaseSysCache(fc_tuple);
		}

		if (fc_or_expr_args != NIL)
		{
			Expr	   *fc_other_parts_constr;

			/*
			 * 使用OR组合非默认分区获得的约束。根据要求，OR的每个参数不包括分区键的NOT NULL测试（以避免其无用的重复）。现在也添加相同的内容。
			 */
			fc_other_parts_constr =
				makeBoolExpr(AND_EXPR,
							 lappend(fc_get_range_nulltest(fc_key),
									 list_length(fc_or_expr_args) > 1
									 ? makeBoolExpr(OR_EXPR, fc_or_expr_args,
													-1)
									 : linitial(fc_or_expr_args)),
							 -1);

			/*
			 * 最后，默认分区包含所有不包含在非默认分区中的内容。
			 */
			fc_result = list_make1(makeBoolExpr(NOT_EXPR,
											 list_make1(fc_other_parts_constr), -1));
		}

		return fc_result;
	}

	/*
	 * 如果这是针对默认的递归调用，我们跳过get_range_nulltest以避免在每个分区的相同键上积累NullTest。
	 */
	if (!fc_for_default)
		fc_result = fc_get_range_nulltest(fc_key);

	/*
	 * 遍历关键列并检查相应的下界和上界数据是否相等，使用该列类型的btree相等运算符。如果相等，我们发出单个 keyCol = common_value 表达式。从第一个下界和上界数据不相等的列开始，我们生成 OR 表达式，如函数的头部注释所示。
	 */
	fc_i = 0;
	fc_partexprs_item = list_head(fc_key->partexprs);
	fc_partexprs_item_saved = fc_partexprs_item;	/* 安抚编译器 */
	forboth(fc_cell1, fc_spec->lowerdatums, fc_cell2, fc_spec->upperdatums)
	{
		EState	   *fc_estate;
		MemoryContext fc_oldcxt;
		Expr	   *fc_test_expr;
		ExprState  *fc_test_exprstate;
		Datum		fc_test_result;
		bool		fc_isNull;

		fc_ldatum = lfirst_node(PartitionRangeDatum, fc_cell1);
		fc_udatum = lfirst_node(PartitionRangeDatum, fc_cell2);

		/*
		 * 由于 get_range_key_properties() 修改 partexprs_item，并且我们可能需要从该函数后面部分的先前表达式重新开始，因此保存当前值。
		 */
		fc_partexprs_item_saved = fc_partexprs_item;

		fc_get_range_key_properties(fc_key, fc_i, fc_ldatum, fc_udatum,
								 &fc_partexprs_item,
								 &fc_keyCol,
								 &fc_lower_val, &fc_upper_val);

		/*
		 * 如果任一值为 NULL，则相应的分区边界要么是 MINVALUE，要么是 MAXVALUE，我们将其视为不相等，因为即使它们相同，也没有共同值可以与关键列相等。
		 */
		if (!fc_lower_val || !fc_upper_val)
			break;

		/* 创建测试表达式 */
		fc_estate = CreateExecutorState();
		fc_oldcxt = MemoryContextSwitchTo(fc_estate->es_query_cxt);
		fc_test_expr = fc_make_partition_op_expr(fc_key, fc_i, BTEqualStrategyNumber,
										   (Expr *) fc_lower_val,
										   (Expr *) fc_upper_val);
		fix_opfuncids((Node *) fc_test_expr);
		fc_test_exprstate = ExecInitExpr(fc_test_expr, NULL);
		fc_test_result = ExecEvalExprSwitchContext(fc_test_exprstate,
												GetPerTupleExprContext(fc_estate),
												&fc_isNull);
		MemoryContextSwitchTo(fc_oldcxt);
		FreeExecutorState(fc_estate);

		/* 如果不相等，则生成 OR 表达式 */
		if (!DatumGetBool(fc_test_result))
			break;

		/*
		 * 最后一关键列的边界不能相等，因为这样的范围分区将永远不被允许定义（否则将具有空范围）。
		 */
		if (fc_i == fc_key->partnatts - 1)
			elog(ERROR, "invalid range bound specification");

		/* 相等，因此生成 keyCol = lower_val 表达式 */
		fc_result = lappend(fc_result,
						 fc_make_partition_op_expr(fc_key, fc_i, BTEqualStrategyNumber,
												fc_keyCol, (Expr *) fc_lower_val));

		fc_i++;
	}

	/* 第一个不相等的 lower_val 和 upper_val 对。 */
	fc_lower_or_start_datum = fc_cell1;
	fc_upper_or_start_datum = fc_cell2;

	/* OR 的分支数量等于剩余关键列的数量。 */
	fc_num_or_arms = fc_key->partnatts - fc_i;
	fc_current_or_arm = 0;
	fc_lower_or_arms = fc_upper_or_arms = NIL;
	fc_need_next_lower_arm = fc_need_next_upper_arm = true;
	while (fc_current_or_arm < fc_num_or_arms)
	{
		List	   *fc_lower_or_arm_args = NIL,
				   *fc_upper_or_arm_args = NIL;

		/* 从第 i 列重新扫描列 */
		fc_j = fc_i;
		fc_partexprs_item = fc_partexprs_item_saved;

		for_both_cell(fc_cell1, fc_spec->lowerdatums, fc_lower_or_start_datum,
					  fc_cell2, fc_spec->upperdatums, fc_upper_or_start_datum)
		{
			PartitionRangeDatum *fc_ldatum_next = NULL,
					   *fc_udatum_next = NULL;

			fc_ldatum = lfirst_node(PartitionRangeDatum, fc_cell1);
			if (lnext(fc_spec->lowerdatums, fc_cell1))
				fc_ldatum_next = castNode(PartitionRangeDatum,
									   lfirst(lnext(fc_spec->lowerdatums, fc_cell1)));
			fc_udatum = lfirst_node(PartitionRangeDatum, fc_cell2);
			if (lnext(fc_spec->upperdatums, fc_cell2))
				fc_udatum_next = castNode(PartitionRangeDatum,
									   lfirst(lnext(fc_spec->upperdatums, fc_cell2)));
			fc_get_range_key_properties(fc_key, fc_j, fc_ldatum, fc_udatum,
									 &fc_partexprs_item,
									 &fc_keyCol,
									 &fc_lower_val, &fc_upper_val);

			if (fc_need_next_lower_arm && fc_lower_val)
			{
				uint16		fc_strategy;

				/*
				 * 对于该分支的非最后列，使用 EQ 运算符。对于该分支的最后一列，使用 GT，除非这是整个边界检查的最后一列，或者下一个边界数据是 MINVALUE，此时使用 GE。
				 */
				if (fc_j - fc_i < fc_current_or_arm)
					fc_strategy = BTEqualStrategyNumber;
				else if (fc_j == fc_key->partnatts - 1 ||
						 (fc_ldatum_next &&
						  fc_ldatum_next->kind == PARTITION_RANGE_DATUM_MINVALUE))
					fc_strategy = BTGreaterEqualStrategyNumber;
				else
					fc_strategy = BTGreaterStrategyNumber;

				fc_lower_or_arm_args = lappend(fc_lower_or_arm_args,
											fc_make_partition_op_expr(fc_key, fc_j,
																   fc_strategy,
																   fc_keyCol,
																   (Expr *) fc_lower_val));
			}

			if (fc_need_next_upper_arm && fc_upper_val)
			{
				uint16		fc_strategy;

				/*
				 * 对于该分支的非最后列，使用 EQ 运算符。对于该分支的最后一列，使用 LT，除非下一个边界数据是 MAXVALUE，此时使用 LE。
				 */
				if (fc_j - fc_i < fc_current_or_arm)
					fc_strategy = BTEqualStrategyNumber;
				else if (fc_udatum_next &&
						 fc_udatum_next->kind == PARTITION_RANGE_DATUM_MAXVALUE)
					fc_strategy = BTLessEqualStrategyNumber;
				else
					fc_strategy = BTLessStrategyNumber;

				fc_upper_or_arm_args = lappend(fc_upper_or_arm_args,
											fc_make_partition_op_expr(fc_key, fc_j,
																   fc_strategy,
																   fc_keyCol,
																   (Expr *) fc_upper_val));
			}

			/*
			 * 我们生成的 OR 参数足够吗？第一个分支考虑剩余列中的第一个，第二个分支考虑剩余列中的前两个，以此类推。
			 */
			++fc_j;
			if (fc_j - fc_i > fc_current_or_arm)
			{
				/*
				 * 如果要考虑的新列是无界的，或者此列是无界的，我们必须不再发出任何分支。
				 */
				if (!fc_lower_val || !fc_ldatum_next ||
					fc_ldatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
					fc_need_next_lower_arm = false;
				if (!fc_upper_val || !fc_udatum_next ||
					fc_udatum_next->kind != PARTITION_RANGE_DATUM_VALUE)
					fc_need_next_upper_arm = false;
				break;
			}
		}

		if (fc_lower_or_arm_args != NIL)
			fc_lower_or_arms = lappend(fc_lower_or_arms,
									list_length(fc_lower_or_arm_args) > 1
									? makeBoolExpr(AND_EXPR, fc_lower_or_arm_args, -1)
									: linitial(fc_lower_or_arm_args));

		if (fc_upper_or_arm_args != NIL)
			fc_upper_or_arms = lappend(fc_upper_or_arms,
									list_length(fc_upper_or_arm_args) > 1
									? makeBoolExpr(AND_EXPR, fc_upper_or_arm_args, -1)
									: linitial(fc_upper_or_arm_args));

		/* 如果下一次迭代没有工作要做，则中断。 */
		if (!fc_need_next_lower_arm && !fc_need_next_upper_arm)
			break;

		++fc_current_or_arm;
	}

	/*
	 * 为每个下界和上界生成 OR 表达式（如果需要），并附加到隐式 AND 列表的表达式中。
	 */
	if (fc_lower_or_arms != NIL)
		fc_result = lappend(fc_result,
						 list_length(fc_lower_or_arms) > 1
						 ? makeBoolExpr(OR_EXPR, fc_lower_or_arms, -1)
						 : linitial(fc_lower_or_arms));
	if (fc_upper_or_arms != NIL)
		fc_result = lappend(fc_result,
						 list_length(fc_upper_or_arms) > 1
						 ? makeBoolExpr(OR_EXPR, fc_upper_or_arms, -1)
						 : linitial(fc_upper_or_arms));

	/*
	 * 如上所述，对于非默认情况，我们返回一个常量 TRUE 的列表。如果在默认情况下递归调用的结果为 NIL，则意味着这是唯一可以容纳关键列的每个值（除 NULL 外）的其他分区。因此我们返回之前跳过的 NullTest 结果。
	 */
	if (fc_result == NIL)
		fc_result = fc_for_default
			? fc_get_range_nulltest(fc_key)
			: list_make1(makeBoolConst(true, false));

	return fc_result;
}

/*
 * get_range_key_properties
 *		返回给定列的范围分区键信息
 *
 * 这是get_qual_for_range的一个子例程，其API相当
 * 专门化于该调用者。
 *
 * 为键列构造一个Expr（返回在*keyCol中）和总量
 * 为下限和上限范围限制（返回在*lower_val和
 * *upper_val中）。对于MINVALUE/MAXVALUE限制，返回NULL而不是
 * Const。所有这些结构都是新分配的palloc。
 *
 * *partexprs_item指向包含在
 * key->partexprs列表中的下一个表达式的单元，或NULL。
 * 返回时可以向前推进。
 */
static void fc_get_range_key_properties(PartitionKey fc_key, int fc_keynum,
						 PartitionRangeDatum *fc_ldatum,
						 PartitionRangeDatum *fc_udatum,
						 ListCell **fc_partexprs_item,
						 Expr **fc_keyCol,
						 Const **fc_lower_val, Const **fc_upper_val)
{
	/* 获取该列的分区键表达式 */
	if (fc_key->partattrs[fc_keynum] != 0)
	{
		*fc_keyCol = (Expr *) makeVar(1,
								   fc_key->partattrs[fc_keynum],
								   fc_key->parttypid[fc_keynum],
								   fc_key->parttypmod[fc_keynum],
								   fc_key->parttypcoll[fc_keynum],
								   0);
	}
	else
	{
		if (*fc_partexprs_item == NULL)
			elog(ERROR, "wrong number of partition key expressions");
		*fc_keyCol = copyObject(lfirst(*fc_partexprs_item));
		*fc_partexprs_item = lnext(fc_key->partexprs, *fc_partexprs_item);
	}

	/* 获取边界的适当Const节点 */
	if (fc_ldatum->kind == PARTITION_RANGE_DATUM_VALUE)
		*fc_lower_val = castNode(Const, copyObject(fc_ldatum->value));
	else
		*fc_lower_val = NULL;

	if (fc_udatum->kind == PARTITION_RANGE_DATUM_VALUE)
		*fc_upper_val = castNode(Const, copyObject(fc_udatum->value));
	else
		*fc_upper_val = NULL;
}

/*
 * get_range_nulltest
 *
 * 非默认的范围分区表目前不允许分区
 * 键为NULL，因此为每个键列发出一个IS NOT NULL表达式。
 */
static List * fc_get_range_nulltest(PartitionKey fc_key)
{
	List	   *fc_result = NIL;
	NullTest   *fc_nulltest;
	ListCell   *fc_partexprs_item;
	int			fc_i;

	fc_partexprs_item = list_head(fc_key->partexprs);
	for (fc_i = 0; fc_i < fc_key->partnatts; fc_i++)
	{
		Expr	   *fc_keyCol;

		if (fc_key->partattrs[fc_i] != 0)
		{
			fc_keyCol = (Expr *) makeVar(1,
									  fc_key->partattrs[fc_i],
									  fc_key->parttypid[fc_i],
									  fc_key->parttypmod[fc_i],
									  fc_key->parttypcoll[fc_i],
									  0);
		}
		else
		{
			if (fc_partexprs_item == NULL)
				elog(ERROR, "wrong number of partition key expressions");
			fc_keyCol = copyObject(lfirst(fc_partexprs_item));
			fc_partexprs_item = lnext(fc_key->partexprs, fc_partexprs_item);
		}

		fc_nulltest = makeNode(NullTest);
		fc_nulltest->arg = fc_keyCol;
		fc_nulltest->nulltesttype = IS_NOT_NULL;
		fc_nulltest->argisrow = false;
		fc_nulltest->location = -1;
		fc_result = lappend(fc_result, fc_nulltest);
	}

	return fc_result;
}

/*
 * compute_partition_hash_value
 *
 * 计算给定分区键值的hash值。
 */
uint64 compute_partition_hash_value(int fc_partnatts, FmgrInfo *fc_partsupfunc, Oid *fc_partcollation,
							 Datum *fc_values, bool *fc_isnull)
{
	int			fc_i;
	uint64		fc_rowHash = 0;
	Datum		fc_seed = UInt64GetDatum(HASH_PARTITION_SEED);

	for (fc_i = 0; fc_i < fc_partnatts; fc_i++)
	{
		/* NULL被忽略 */
		if (!fc_isnull[fc_i])
		{
			Datum		fc_hash;

			Assert(OidIsValid(fc_partsupfunc[fc_i].fn_oid));

			/*
			 * 通过调用每个分区键
			 * 属性的各自数据类型特定的hash函数
			 * 来计算每个数据值的hash。
			 */
			fc_hash = FunctionCall2Coll(&fc_partsupfunc[fc_i], fc_partcollation[fc_i],
									 fc_values[fc_i], fc_seed);

			/* 形成一个单一的64位hash值 */
			fc_rowHash = hash_combine64(fc_rowHash, DatumGetUInt64(fc_hash));
		}
	}

	return fc_rowHash;
}

/*
 * satisfies_hash_partition
 *
 * 这是一个可供SQL调用的函数，用于hash分区约束。
 * 前三个参数是父表OID、模数和余数。
 * 剩余参数是分区列的值（或
 * 表达式）；这些被hash，结果通过调用hash_combine64
 * 组合成一个单一的hash值。
 *
 * 如果计算出的单一hash值在给定模数
 * 除法时产生的余数等于给定余数，则返回true，否则返回false。
 * 注意：重要的是，这永远不应返回null，因为约束机制
 * 会将其视为“通过”。
 *
 * 参见get_qual_for_hash()的用法。
 */
Datum satisfies_hash_partition(PG_FUNCTION_ARGS)
{
	typedef struct ColumnsHashData
	{
		Oid			relid;
		int			nkeys;
		Oid			variadic_type;
		int16		variadic_typlen;
		bool		variadic_typbyval;
		char		variadic_typalign;
		Oid			partcollid[PARTITION_MAX_KEYS];
		FmgrInfo	partsupfunc[FLEXIBLE_ARRAY_MEMBER];
	} ColumnsHashData;
	Oid			fc_parentId;
	int			fc_modulus;
	int			fc_remainder;
	Datum		fc_seed = UInt64GetDatum(HASH_PARTITION_SEED);
	ColumnsHashData *fc_my_extra;
	uint64		fc_rowHash = 0;

	/* 如果父OID、模数或余数为NULL，则返回false。 */
	if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2))
		PG_RETURN_BOOL(false);
	fc_parentId = PG_GETARG_OID(0);
	fc_modulus = PG_GETARG_INT32(1);
	fc_remainder = PG_GETARG_INT32(2);

	/* 进行模数和余数的合理性检查。 */
	if (fc_modulus <= 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("modulus for hash partition must be an integer value greater than zero")));
	if (fc_remainder < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("remainder for hash partition must be an integer value greater than or equal to zero")));
	if (fc_remainder >= fc_modulus)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("remainder for hash partition must be less than modulus")));

	/*
	 * 缓存hash函数信息。
	 */
	fc_my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL || fc_my_extra->relid != fc_parentId)
	{
		Relation	fc_parent;
		PartitionKey fc_key;
		int			fc_j;

		/* 打开父关系并获取分区键信息 */
		fc_parent = relation_open(fc_parentId, AccessShareLock);
		fc_key = RelationGetPartitionKey(fc_parent);

		/* 拒绝未进行hash分区的父表。 */
		if (fc_key == NULL || fc_key->strategy != PARTITION_STRATEGY_HASH)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("\"%s\" is not a hash partitioned table",
							get_rel_name(fc_parentId))));

		if (!get_fn_expr_variadic(fcinfo->flinfo))
		{
			int			fc_nargs = PG_NARGS() - 3;

			/* 如果列值数量不正确则投诉 */
			if (fc_key->partnatts != fc_nargs)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("number of partitioning columns (%d) does not match number of partition keys provided (%d)",
								fc_key->partnatts, fc_nargs)));

			/* 为我们的缓存分配空间 */
			fcinfo->flinfo->fn_extra =
				MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
									   offsetof(ColumnsHashData, partsupfunc) +
									   sizeof(FmgrInfo) * fc_nargs);
			fc_my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
			fc_my_extra->relid = fc_parentId;
			fc_my_extra->nkeys = fc_key->partnatts;
			memcpy(fc_my_extra->partcollid, fc_key->partcollation,
				   fc_key->partnatts * sizeof(Oid));

			/* 检查参数类型并保存fmgr_infos */
			for (fc_j = 0; fc_j < fc_key->partnatts; ++fc_j)
			{
				Oid			fc_argtype = get_fn_expr_argtype(fcinfo->flinfo, fc_j + 3);

				if (fc_argtype != fc_key->parttypid[fc_j] && !IsBinaryCoercible(fc_argtype, fc_key->parttypid[fc_j]))
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
							 errmsg("column %d of the partition key has type %s, but supplied value is of type %s",
									fc_j + 1, format_type_be(fc_key->parttypid[fc_j]), format_type_be(fc_argtype))));

				fmgr_info_copy(&fc_my_extra->partsupfunc[fc_j],
							   &fc_key->partsupfunc[fc_j],
							   fcinfo->flinfo->fn_mcxt);
			}
		}
		else
		{
			ArrayType  *fc_variadic_array = PG_GETARG_ARRAYTYPE_P(3);

			/* 为我们的缓存分配空间 — 在此情况下仅一个FmgrInfo */
			fcinfo->flinfo->fn_extra =
				MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
									   offsetof(ColumnsHashData, partsupfunc) +
									   sizeof(FmgrInfo));
			fc_my_extra = (ColumnsHashData *) fcinfo->flinfo->fn_extra;
			fc_my_extra->relid = fc_parentId;
			fc_my_extra->nkeys = fc_key->partnatts;
			fc_my_extra->variadic_type = ARR_ELEMTYPE(fc_variadic_array);
			get_typlenbyvalalign(fc_my_extra->variadic_type,
								 &fc_my_extra->variadic_typlen,
								 &fc_my_extra->variadic_typbyval,
								 &fc_my_extra->variadic_typalign);
			fc_my_extra->partcollid[0] = fc_key->partcollation[0];

			/* 检查参数类型 */
			for (fc_j = 0; fc_j < fc_key->partnatts; ++fc_j)
				if (fc_key->parttypid[fc_j] != fc_my_extra->variadic_type)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
							 errmsg("column %d of the partition key has type \"%s\", but supplied value is of type \"%s\"",
									fc_j + 1,
									format_type_be(fc_key->parttypid[fc_j]),
									format_type_be(fc_my_extra->variadic_type))));

			fmgr_info_copy(&fc_my_extra->partsupfunc[0],
						   &fc_key->partsupfunc[0],
						   fcinfo->flinfo->fn_mcxt);
		}

		/* 持有锁直到提交 */
		relation_close(fc_parent, NoLock);
	}

	if (!OidIsValid(fc_my_extra->variadic_type))
	{
		int			fc_nkeys = fc_my_extra->nkeys;
		int			fc_i;

		/*
		 * 对于非可变参数调用，参数的数量和
		 * 类型在不同调用之间不能改变，因此
		 * 避免在这里重新检查的开销。
		 */

		for (fc_i = 0; fc_i < fc_nkeys; fc_i++)
		{
			Datum		fc_hash;

			/* 键从函数的第四个参数开始。 */
			int			fc_argno = fc_i + 3;

			if (PG_ARGISNULL(fc_argno))
				continue;

			fc_hash = FunctionCall2Coll(&fc_my_extra->partsupfunc[fc_i],
									 fc_my_extra->partcollid[fc_i],
									 PG_GETARG_DATUM(fc_argno),
									 fc_seed);

			/* 形成一个单一的64位hash值 */
			fc_rowHash = hash_combine64(fc_rowHash, DatumGetUInt64(fc_hash));
		}
	}
	else
	{
		ArrayType  *fc_variadic_array = PG_GETARG_ARRAYTYPE_P(3);
		int			fc_i;
		int			fc_nelems;
		Datum	   *fc_datum;
		bool	   *fc_isnull;

		deconstruct_array(fc_variadic_array,
						  fc_my_extra->variadic_type,
						  fc_my_extra->variadic_typlen,
						  fc_my_extra->variadic_typbyval,
						  fc_my_extra->variadic_typalign,
						  &fc_datum, &fc_isnull, &fc_nelems);

		/* 如果列值数量不正确则投诉 */
		if (fc_nelems != fc_my_extra->nkeys)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("number of partitioning columns (%d) does not match number of partition keys provided (%d)",
							fc_my_extra->nkeys, fc_nelems)));

		for (fc_i = 0; fc_i < fc_nelems; fc_i++)
		{
			Datum		fc_hash;

			if (fc_isnull[fc_i])
				continue;

			fc_hash = FunctionCall2Coll(&fc_my_extra->partsupfunc[0],
									 fc_my_extra->partcollid[0],
									 fc_datum[fc_i],
									 fc_seed);

			/* 形成一个单一的64位hash值 */
			fc_rowHash = hash_combine64(fc_rowHash, DatumGetUInt64(fc_hash));
		}
	}

	PG_RETURN_BOOL(fc_rowHash % fc_modulus == fc_remainder);
}
